/**
 * @file html5.js
 */
從 './tech.js' 導入技術;
import * as Dom from '../utils/dom.js';
import * as Url from '../utils/url.js';
從 '../utils/log.js' 導入日誌;
從 '../utils/browser.js' 導入 * 作為瀏覽器;
從“全局/文檔”導入文檔;
從“全局/窗口”導入窗口;
從'../utils/obj'導入{assign};
從 '../utils/merge-options.js' 導入 mergeOptions;
從 '../utils/string-cases.js' 導入 {toTitleCase};
從 '../tracks/track-types' 導入 {NORMAL as TRACK_TYPES, REMOTE};
從 './setup-sourceset' 導入 setupSourceset;
從 '../utils/define-lazy-property.js' 導入 defineLazyProperty;
從'../utils/promise'導入{silencePromise};

/**
 * HTML5 媒體控制器 - HTML5 媒體 API 的包裝器
 *
 * @mixes Tech~SourceHandlerAdditions
 * @extends 技術
 */
類 Html5 擴展技術 {

  /**
  * 創建該技術的一個實例。
  *
  * @param {對象} [選項]
  * 播放器選項的鍵/值存儲。
  *
  * @param {Component~ReadyCallback} 準備好了
  * `HTML5` 技術準備就緒時調用的回調函數。
  */
  構造函數(選項,準備就緒){
    超級(選項,準備就緒);

    const source = options.source;
    讓 crossoriginTracks = false;

    this.featuresVideoFrameCallback = this.featuresVideoFrameCallback && this.el_.tagName === '視頻';

    // 如果提供了源,則設置源
    // 1) 檢查源是否是新的(如果不是,我們希望保留原件,這樣播放就不會中斷)
    // 2) 檢查標籤的網絡狀態是否在初始化時失敗,如果是,則重置源
    // 無論如何,錯誤被觸發。
    if (source && (this.el_.currentSrc !== source.src || (options.tag && options.tag.initNetworkState_ === 3))) {
      this.setSource(來源);
    }其他{
      this.handleLateInit_(this.el_);
    }

    // 在 late sourceset/init 之後設置 sourceset
    如果(選項。enableSourceset){
      this.setupSourcesetHandling_();
    }

    this.isScrubbing_ = false;

    如果 (this.el_.hasChildNodes()) {

      const nodes = this.el_.childNodes;
      讓 nodesLength = nodes.length;
      const removeNodes = [];

      而(節點長度--){
        const 節點 = 節點 [節點長度];
        const nodeName = node.nodeName.toLowerCase();

        如果(節點名稱==='跟踪'){
          如果(!this.featuresNativeTextTracks){
            // 清空視頻標籤軌道,以便內置播放器不使用它們。
            // 這可能不夠快,無法阻止 HTML5 瀏覽器讀取標籤
            // 所以如果我們手動執行,我們需要關閉任何默認軌道
            // 字幕和副標題。視頻元素.textTracks
            removeNodes.push(節點);
          }其他{
            // 將 HTMLTrackElement 和 TextTrack 存儲到遠程列表
            this.remoteTextTrackEls().addTrackElement_(node);
            this.remoteTextTracks().addTrack(node.track);
            this.textTracks().addTrack(node.track);
            如果(!crossoriginTracks &&
                !this.el_.hasAttribute('crossorigin') &&
                Url.isCrossOrigin(node.src)) {
              跨域曲目=真;
            }
          }
        }
      }

      對於(讓 i = 0;i < removeNodes.length;i++){
        this.el_.removeChild(removeNodes[i]);
      }
    }

    this.proxyNativeTracks_();
    如果(this.featuresNativeTextTracks && crossoriginTracks){
      log.warn('正在從另一個源加載文本軌道,但未使用 crossorigin 屬性。\\n' +
            '這可能會阻止加載文本軌道。');
    }

    // 防止 iOS Safari 在本機播放期間禁用元數據文本軌道
    this.restoreMetadataTracksInIOSNativePlayer_();

    // 確定是否應該使用本機控件
    // 我們的目標應該是讓自定義控件在移動設備上隨處可見
    // 所以我們可以一起刪除它。現在這將阻止自定義
    // 支持觸控的筆記本電腦(如 Chrome Pixel)上的控件
    如果((瀏覽器.TOUCH_ENABLED || 瀏覽器.IS_IPHONE ||
        browser.IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
      this.setControls(true);
    }

    // 在 iOS 上,我們要代理 `webkitbeginfullscreen` 和 `webkitendfullscreen`
    // 進入 `fullscreenchange` 事件
    this.proxyWebkitFullscreen_();

    this.triggerReady();
  }

  /**
   * 處理 `HTML5` 媒體元素並刪除所有曲目。
   */
  處置(){
    如果 (this.el_ && this.el_.resetSourceset_) {
      this.el_.resetSourceset_();
    }
    Html5.disposeMediaElement(this.el_);
    this.options_ = null;

    // 技術人員將處理模擬曲目列表的清除
    super.dispose();
  }

  /**
   * 修改媒體元素,以便我們可以檢測何時
   * 來源已更改。在源更改後立即觸發 `sourceset`
   */
  setupSourcesetHandling_() {
    setupSourceset(這個);
  }

  /**
   * 在 iOS Safari 本地播放器中啟用字幕軌道時,所有其他
   * 曲目被禁用(包括元數據曲目),這會使它們的所有
   * 關聯的提示點。這會將元數據軌道恢復到全屏前
   * 在這些情況下說明,以免不必要地丟失提示點。
   *
   * @私人的
   */
  restoreMetadataTracksInIOSNativePlayer_() {
    const textTracks = this.textTracks();
    讓 metadataTracksPreFullscreenState;

    // 捕獲每個元數據軌道當前狀態的快照
    const takeMetadataTrackSnapshot = () => {
      metadataTracksPreFullscreenState = [];

      對於(讓 i = 0;i < textTracks.length;i++){
        const track = textTracks[i];

        如果(track.kind ==='元數據'){
          metadataTracksPreFullscreenState.push({
            追踪,
            存儲模式:track.mode
          });
        }
      }
    };

    // 快照每個元數據軌道的初始狀態,並更新快照
    // 每次都有一個軌道 'change' 事件
    takeMetadataTrackSnapshot();
    textTracks.addEventListener('change', takeMetadataTrackSnapshot);

    this.on('dispose', () => textTracks.removeEventListener('change', takeMetadataTrackSnapshot));

    const restoreTrackMode = () => {
      for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
        const storedTrack = metadataTracksPreFullscreenState[i];

        如果(storedTrack.track.mode === '禁用' && storedTrack.track.mode !== storedTrack.storedMode){
          storedTrack.track.mode = storedTrack.storedMode;
        }
      }
      // 我們只希望這個處理程序在第一個 'change' 事件上執行
      textTracks.removeEventListener('change', restoreTrackMode);
    };

    // 當我們進入全屏播放時,停止更新快照和
    // 將所有軌道模式恢復到全屏前的狀態
    this.on('webkitbeginfullscreen', () => {
      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);

      // 在添加之前刪除監聽器以防萬一它之前沒有被刪除
      textTracks.removeEventListener('change', restoreTrackMode);
      textTracks.addEventListener('change', restoreTrackMode);
    });

    // 離開全屏後再次開始更新快照
    this.on('webkitendfullscreen', () => {
      // 在添加之前刪除監聽器以防萬一它之前沒有被刪除
      textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
      textTracks.addEventListener('change', takeMetadataTrackSnapshot);

      // 刪除 restoreTrackMode 處理程序,以防它在全屏播放期間未被觸發
      textTracks.removeEventListener('change', restoreTrackMode);
    });
  }

  /**
   * 嘗試強制覆蓋給定類型的軌道
   *
   * @param {string} type - 要覆蓋的軌道類型,可能的值包括“音頻”,
   *“視頻”和“文本”。
   * @param {boolean} override - 如果設置為 true 原生音頻/視頻將被覆蓋,
   * 否則可能會使用原生音頻/視頻。
   * @私人的
   */
  overrideNative_(類型,覆蓋){
    // 如果沒有行為改變,則不要添加/刪除偵聽器
    如果(覆蓋!== this[`featuresNative${type}Tracks`]){
      返回;
    }

    const lowerCaseType = type.toLowerCase();

    如果(這個[`${lowerCaseType}TracksListeners_`]){
      Object.keys(this[`${lowerCaseType}TracksListeners_`]).forEach((eventName) => {
        const elTracks = this.el()[`${lowerCaseType}Tracks`];

        elTracks.removeEventListener(eventName, this[`${lowerCaseType}TracksListeners_`][eventName]);
      });
    }

    這個[`featuresNative${type}Tracks`] = !override;
    這個[`${lowerCaseType}TracksListeners_`] = null;

    this.proxyNativeTracksForType_(lowerCaseType);
  }

  /**
   * 嘗試強制覆蓋本機音軌。
   *
   * @param {boolean} override - 如果設置為 true 原生音頻將被覆蓋,
   *否則可能會使用本機音頻。
   */
  overrideNativeAudioTracks(覆蓋){
    this.overrideNative_('Audio', override);
  }

  /**
   * 嘗試強制覆蓋本機視頻軌道。
   *
   * @param {boolean} override - 如果設置為 true 原生視頻將被覆蓋,
   *否則可能會使用原生視頻。
   */
  overrideNativeVideoTracks(覆蓋){
    this.overrideNative_('Video', override);
  }

  /**
   * 將給定類型的本機曲目列表事件代理到我們的曲目
   * 列出我們正在播放的瀏覽器是否支持該類型的曲目列表。
   *
   * @param {string} name - 軌道類型;值包括“音頻”、“視頻”和“文本”
   * @私人的
   */
  proxyNativeTracksForType_(名稱){
    const props = TRACK_TYPES[名稱];
    const elTracks = this.el()[props.getterName];
    const techTracks = this[props.getterName]();

    如果 (!this[`featuresNative${props.capitalName}Tracks`] ||
        !elTracks ||
        !elTracks.addEventListener) {
      返回;
    }
    const 監聽器 = {
      改變:(e)=> {
        const 事件 = {
          類型:'改變',
          目標:techTracks,
          當前目標:techTracks,
          srcElement:techTracks
        };

        techTracks.trigger(事件);

        // 如果我們是文本軌道變化事件,我們也應該通知
        // 遠程文本軌道列表。這可能會導致誤報
        // 如果我們要在非遠程軌道上獲得更改事件並且
        // 我們觸發了遠程文本軌道列表上的事件,但它沒有
        // 包含那條軌道。但是,最佳實踐意味著遍歷
        // 軌道列表並蒐索適當的模式值,因此,
        // 這不應該成為問題
        如果(名稱==='文本'){
          這個[REMOTE.remoteText.getterName]().trigger(事件);
        }
      },
      添加曲目(e){
        techTracks.addTrack(e.track);
      },
      removetrack(e) {
        techTracks.removeTrack(e.track);
      }
    };
    const removeOldTracks = function() {
      const removeTracks = [];

      對於(讓 i = 0;i < techTracks.length;i++){
        讓發現=假;

        對於(設 j = 0;j < elTracks.length;j++){
          如果(elTracks[j] === techTracks[i]){
            發現=真;
            休息;
          }
        }

        如果(!發現){
          removeTracks.push(techTracks[i]);
        }
      }

      而(removeTracks.length){
        techTracks.removeTrack(removeTracks.shift());
      }
    };

    this[props.getterName + 'Listeners_'] = listeners;

    Object.keys(listeners).forEach((eventName) => {
      const listener = listeners[事件名稱];

      elTracks.addEventListener(eventName, listener);
      this.on('dispose', (e) => elTracks.removeEventListener(eventName, listener));
    });

    // 刪除不再使用的(原生)軌道
    this.on('loadstart', removeOldTracks);
    this.on('dispose', (e) => this.off('loadstart', removeOldTracks));
  }

  /**
   * 如果我們正在播放的瀏覽器,則將所有本機曲目列表事件代理到我們的曲目列表
   * 支持該類型的曲目列表。
   *
   * @私人的
   */
  proxyNativeTracks_() {
    TRACK_TYPES.names.forEach((名稱) => {
      this.proxyNativeTracksForType_(名稱);
    });
  }

  /**
   * 創建 `Html5` Tech 的 DOM 元素。
   *
   * @return {元素}
   * 被創建的元素。
   */
  創建El() {
    讓 el = this.options_.tag;

    // 檢查此瀏覽器是否支持將元素移動到框中。
    // 在 iPhone 上,如果你移動元素,視頻將會中斷,
    // 所以我們必須創建一個全新的元素。
    // 如果我們攝取播放器 div,則不需要移動媒體元素。
    如果 (!el ||
        !(this.options_.playerElIngest ||
          this.movingMediaElementInDOM)) {

      // 如果原始標籤仍然存在,則克隆並刪除它。
      如果(EL){
        const clone = el.cloneNode(true);

        如果(el.parentNode){
          el.parentNode.insertBefore(clone, el);
        }
        Html5.disposeMediaElement(el);
        el = 克隆;

      }其他{
        el = document.createElement('視頻');

        // 確定是否應該使用本機控件
        const tagAttributes = this.options_.tag && Dom.getAttributes(this.options_.tag);
        const attributes = mergeOptions({}, tagAttributes);

        如果 (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
          刪除屬性。控件;
        }

        Dom.setAttributes(
          埃爾,
          分配(屬性,{
            id: this.options_.techId,
            類:'vjs-tech'
          })
        );
      }

      el.playerId = this.options_.playerId;
    }

    if (typeof this.options_.preload !== 'undefined') {
      Dom.setAttribute(el, 'preload', this.options_.preload);
    }

    如果(this.options_.disablePictureInPicture !==未定義){
      el.disablePictureInPicture = this.options_.disablePictureInPicture;
    }

    // 更新特定的標籤設置,以防它們被覆蓋
    // `autoplay` 必須是 *last* 以便出現 `muted` 和 `playsinline`
    // 當 iOS/Safari 或其他瀏覽器嘗試自動播放時。
    const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];

    for (let i = 0; i < settingsAttrs.length; i++) {
      const attr = settingAttrs[i];
      const value = this.options_[attr];

      如果(類型值!=='未定義'){
        如果(值){
          Dom.setAttribute(el, attr, attr);
        }其他{
          Dom.removeAttribute(el, attr);
        }
        el[屬性] = 值;
      }
    }

    返回 el;
  }

  /**
   * 如果 loadstart 事件已經觸發,這將在 videojs 之前被觸發
   * 準備好。何時會發生這種情況的兩個已知示例是:
   * 1。如果我們在開始加載後加載播放對象
   * 2。媒體已經在播放(通常自動播放)然後
   *
   * 此函數將觸發另一個 loadstart,以便 videojs 可以趕上。
   *
   * @fires Tech#loadstart
   *
   * @return {未定義}
   * 什麼都不返回。
   */
  handleLateInit_(el) {
    如果(el.networkState === 0 || el.networkState === 3){
      // 視頻元素還沒有開始加載源
      // 或者沒有找到資源
      返回;
    }

    如果(el.readyState === 0){
      // NetworkState 是同步設置的,但是 loadstart 是在
      // 當前堆棧的末尾,通常在 setInterval(fn, 0) 之前。
      // 所以此時我們知道 loadstart 可能已經觸發或正在觸發
      // 即將開火,無論哪種方式,玩家都還沒有看到它。
      // 我們不想在這裡過早地觸發 loadstart 並導致
      // double loadstart 所以我們等著看它是否發生在現在
      // 和下一個循環,如果沒有則觸發它。
      // 但是,我們還想確保它在加載元數據之前觸發
      // 這也可能發生在現在和下一個循環之間,所以我們將
      // 也要注意這一點。
      讓 loadstartFired = false;
      const setLoadstartFired = function() {
        loadstartFired = true;
      };

      this.on('loadstart', setLoadstartFired);

      const triggerLoadstart = function() {
        // 我們確實錯過了最初的 loadstart。確保播放器
        // 在 loadedmetadata 之前看到 loadstart
        如果(!loadstartFired){
          this.trigger('loadstart');
        }
      };

      this.on('loadedmetadata', triggerLoadstart);

      這個。準備好(功能(){
        this.off('loadstart', setLoadstartFired);
        this.off('loadedmetadata', triggerLoadstart);

        如果(!loadstartFired){
          // 我們確實錯過了原始的原生 loadstart。現在開火。
          this.trigger('loadstart');
        }
      });

      返回;
    }

    // 從這裡我們知道 loadstart 已經觸發,但我們錯過了它。
    // 如果我們加倍,其他 readyState 事件就不是什麼大問題
    // 他們,所以不會像 loadstart 那樣麻煩來防止
    // 除非我們找到理由。
    const eventsToTrigger = ['loadstart'];

    // 加載的元數據:新等於 HAVE_METADATA (1) 或更大
    eventsToTrigger.push('loadedmetadata');

    // 加載數據:新增加到 HAVE_CURRENT_DATA (2) 或更大
    如果(el.readyState >= 2){
      eventsToTrigger.push('loadeddata');
    }

    // canplay: 新增加到 HAVE_FUTURE_DATA (3) 或更大
    如果(el.readyState >= 3){
      eventsToTrigger.push('canplay');
    }

    // canplaythrough:新等於 HAVE_ENOUGH_DATA (4)
    如果(el.readyState >= 4){
      eventsToTrigger.push('canplaythrough');
    }

    // 我們仍然需要給玩家時間來添加事件監聽器
    這個。準備好(功能(){
      eventsToTrigger.forEach(函數(類型){
        這個。觸發器(類型);
      }, 這);
    });
  }

  /**
   * 設置我們是否正在擦洗。
   * 這用於決定我們是否應該使用 `fastSeek` 或不。
   * `fastSeek` 用於在 Safari 瀏覽器上提供特技播放。
   *
   * @param {boolean} isScrubbing
   * - true 因為我們目前正在擦洗
   * - false 因為我們不再擦洗
   */
  setScrubbing(isScrubbing){
    this.isScrubbing_ = isScrubbing;
  }

  /**
   * 獲取我們是否正在擦洗。
   *
   * @return {boolean} isScrubbing
   * - true 因為我們目前正在擦洗
   * - false 因為我們不再擦洗
   */
  擦洗(){
    返回 this.isScrubbing_;
  }

  /**
   * 為 `HTML5` 技術設置當前時間。
   *
   * @param {number} 秒
   * 將媒體的當前時間設置為此。
   */
  設置當前時間(秒){
    嘗試{
      如果(this.isScrubbing_ && this.el_.fastSeek && browser.IS_ANY_SAFARI){
        this.el_.fastSeek(秒);
      }其他{
        this.el_.currentTime = seconds;
      }
    } 抓住 (e) {
      log(e, '視頻還沒有準備好。(Video.js)');
      // this.warning(VideoJS.warnings.videoNotReady);
    }
  }

  /**
   * 獲取 HTML5 媒體元素的當前持續時間。
   *
   * @return {數字}
   * 媒體的持續時間,如果沒有持續時間則為 0。
   */
  期間() {
    // Android Chrome 將報告 VOD HLS 的持續時間為 Infinity 直到之後
    // 播放已經開始,錯誤地觸發了實時顯示。
    // 如果播放還沒有開始返回 NaN 並觸發一次 durationupdate
    // 可以可靠地知道持續時間。
    如果 (
      this.el_.duration ===無限&&
      瀏覽器.IS_ANDROID &&
      瀏覽器.IS_CHROME &&
      這個.el_.currentTime === 0
    ) {
      // 等待 currentTime > 0 的第一個 `timeupdate` - 可能有
      // 多個為 0
      const checkProgress = () => {
        如果 (this.el_.currentTime > 0) {
          // 觸發真正直播視頻的持續時間變化
          如果(this.el_.duration === Infinity){
            this.trigger('durationchange');
          }
          this.off('timeupdate', checkProgress);
        }
      };

      this.on('timeupdate', checkProgress);
      返回南;
    }
    返回 this.el_.duration ||南;
  }

  /**
   * 獲取 HTML5 媒體元素的當前寬度。
   *
   * @return {數字}
   * HTML5 媒體元素的寬度。
   */
  寬度() {
    返回 this.el_.offsetWidth;
  }

  /**
   * 獲取 HTML5 媒體元素的當前高度。
   *
   * @return {數字}
   * HTML5 媒體元素的高度。
   */
  高度() {
    返回 this.el_.offsetHeight;
  }

  /**
   * 代理 iOS `webkitbeginfullscreen` 和 `webkitendfullscreen` 進入
   * `fullscreenchange` 事件。
   *
   * @私人的
   * @fires fullscreenchange
   * @listens webkitendfullscreen
   * @listens webkitbeginfullscreen
   * @listens webkitbeginfullscreen
   */
  proxyWebkitFullscreen_() {
    如果(!(this.el_中的'webkitDisplayingFullscreen')){
      返回;
    }

    const endFn = function() {
      this.trigger('fullscreenchange', { isFullscreen: false });
      // 當存在全屏時,Safari 有時會在視頻元素上設置控件。
      如果 (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
        this.el_.controls = false;
      }
    };

    const beginFn = function() {
      if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== '畫中畫') {
        this.one('webkitendfullscreen', endFn);

        this.trigger('fullscreenchange', {
          是全屏:真,
          // 設置一個標誌以防其他技術觸發全屏更改
          nativeIOS全屏:真
        });
      }
    };

    this.on('webkitbeginfullscreen', beginFn);
    this.on('處置', () => {
      this.off('webkitbeginfullscreen', beginFn);
      this.off('webkitendfullscreen', endFn);
    });
  }

  /**
   * 檢查當前播放設備是否支持全屏。
   *
   * @return {布爾值}
   * - 如果支持全屏則為真。
   * - 如果不支持全屏則為假。
   */
  支持全屏(){
    if (typeof this.el_.webkitEnterFullScreen === '函數') {
      const userAgent = window.navigator && window.navigator.userAgent || '';

      // 似乎在 Chromium/Chrome && Safari in Leopard 中被破壞
      if ((/Android/).test(userAgent) || !(/Chrome|Mac OS X 10.5/).test(userAgent)) {
        返回真;
      }
    }
    返回假;
  }

  /**
   * 請求 `HTML5` 技術進入全屏。
   */
  進入全屏(){
    const video = this.el_;

    如果(video.paused && video.networkState <= video.HAVE_METADATA){
      // 嘗試啟動視頻元素以進行編程訪問
      // 這在桌面上不是必需的,但不應該造成傷害
      silencePromise(this.el_.play());

      // 在過渡到全屏期間同步播放和暫停
      // 可以讓 iOS ~6.1 設備進入播放/暫停循環
      this.setTimeout(函數() {
        視頻.暫停();
        嘗試{
          video.webkitEnterFullScreen();
        } 抓住 (e) {
          this.trigger('全屏錯誤', e);
        }
      }, 0);
    }其他{
      嘗試{
        video.webkitEnterFullScreen();
      } 抓住 (e) {
        this.trigger('全屏錯誤', e);
      }
    }
  }

  /**
   * 請求 `HTML5` 技術退出全屏。
   */
  退出全屏(){
    如果(!this.el_.webkitDisplayingFullscreen){
      this.trigger('fullscreenerror', new Error('視頻不是全屏'));
      返回;
    }

    this.el_.webkitExitFullScreen();
  }

  /**
   * 創建一個始終位於其他窗口之上的浮動視頻窗口,以便用戶可以
   * 在與其他內容網站互動時繼續消費媒體,或
   * 他們設備上的應用程序。
   *
   * @see [規範]{@link https://wicg.github.io/picture-in-picture}
   *
   * @return {承諾}
   * 帶有畫中畫窗口的承諾。
   */
  請求畫中畫(){
    返回 this.el_.requestPictureInPicture();
  }

  /**
   * 本機 requestVideoFrameCallback(如果瀏覽器/技術支持)或回退
   * 播放 DRM 時不要在 Safari 上使用 rVCF,因為它不會觸發
   * 需要比構造函數晚檢查
   * 對於在 Fairplay 源之後加載的清晰源,這將是誤報
   *
   * @param {function} cb 要調用的函數
   * @return {number} 請求的id
   */
  requestVideoFrameCallback(cb) {
    如果(this.featuresVideoFrameCallback && !this.el_.webkitKeys){
      返回 this.el_.requestVideoFrameCallback(cb);
    }
    返回 super.requestVideoFrameCallback(cb);
  }

  /**
   * 本機或後備 requestVideoFrameCallback
   *
   * @param {number} id 要取消的請求id
   */
  cancelVideoFrameCallback(id) {
    如果(this.featuresVideoFrameCallback && !this.el_.webkitKeys){
      this.el_.cancelVideoFrameCallback(id);
    }其他{
      super.cancelVideoFrameCallback(id);
    }
  }

  /**
   * `Html5` Tech 源對象的 getter/setter。
   * > 注意:請使用{@link Html5#setSource}
   *
   * @param {Tech~SourceObject} [src]
   * 您要在 `HTML5` 技術元素上設置的源對象。
   *
   * @return {Tech~SourceObject|undefined}
   * - 未傳入源時的當前源對象。
   * - 設置時未定義
   *
   * @deprecated 從版本 5 開始。
   */
  源(源){
    如果(源===未定義){
      返回這個.el_.src;
    }

    // 通過 `src` 而不是 `setSrc` 設置 src 將被棄用
    this.setSrc(src);
  }

  /**
   *通過刪除所有來源然後調用來重置技術
   * {@link Html5.resetMediaElement}。
   */
  重置() {
    Html5.resetMediaElement(this.el_);
  }

  /**
   * 獲取 `HTML5` 技術的當前源代碼。回退到返回來源
   * HTML5 媒體元素。
   *
   * @return {Tech~SourceObject}
   * 來自 HTML5 技術的當前源對象。回退到
   * 元素來源。
   */
  當前源(){
    如果(this.currentSource_){
      返回 this.currentSource_.src;
    }
    返回 this.el_.currentSrc;
  }

  /**
   * 為 HTML5 媒體元素設置控件屬性。
   *
   * @param {string} 值
   * 將控件屬性設置為的值
   */
  設置控件(值){
    this.el_.controls = !!val;
  }

  /**
   * 創建並返回一個遠程 {@link TextTrack} 對象。
   *
   * @param {string} 種類
   * `TextTrack` 類型(字幕、字幕、說明、章節或元數據)
   *
   * @param {字符串} [標籤]
   * 用於識別文本軌道的標籤
   *
   * @param {字符串} [語言]
   * 兩個字母的語言縮寫
   *
   * @return {TextTrack}
   * 創建的 TextTrack。
   */
  addTextTrack(種類,標籤,語言){
    如果(!this.featuresNativeTextTracks){
      返回 super.addTextTrack(種類、標籤、語言);
    }

    返回 this.el_.addTextTrack(種類、標籤、語言);
  }

  /**
   * 創建本機 TextTrack 或模擬 TextTrack,具體取決於
   * 關於 `featuresNativeTextTracks` 的值
   *
   * @param {Object} 選項
   * 該對象應包含用於初始化 TextTrack 的選項。
   *
   * @param {string} [options.kind]
   * `TextTrack` 類型(字幕、字幕、說明、章節或元數據)。
   *
   * @param {string} [選項.標籤]
   * 用於識別文本軌道的標籤
   *
   * @param {string} [選項.語言]
   * 兩個字母的語言縮寫。
   *
   * @param {boolean} [options.default]
   * 默認此軌道打開。
   *
   * @param {string} [options.id]
   * 分配此軌道的內部 ID。
   *
   * @param {string} [options.src]
   * 軌道的源 url。
   *
   * @return {HTMLTrackElement}
   * 創建的軌道元素。
   */
  createRemoteTextTrack(選項){
    如果(!this.featuresNativeTextTracks){
      返回 super.createRemoteTextTrack(選項);
    }
    const htmlTrackElement = document.createElement('track');

    如果(選項。種類){
      htmlTrackElement.kind = options.kind;
    }
    如果(選項。標籤){
      htmlTrackElement.label = options.label;
    }
    如果(選項。語言||選項。srclang){
      htmlTrackElement.srclang = options.language ||選項.srclang;
    }
    如果(選項。默認){
      htmlTrackElement.default = options.default;
    }
    如果(選項。id){
      htmlTrackElement.id = options.id;
    }
    如果(選項.src){
      htmlTrackElement.src = options.src;
    }

    返回 htmlTrackElement;
  }

  /**
   * 創建一個遠程文本軌道對象並返回一個 html 軌道元素。
   *
   * @param {Object} options 對象應該包含值
   * 種類、語言、標籤和 src(WebVTT 文件的位置)
   * @param {boolean} [manualCleanup=true] 如果設置為 false,TextTrack 將是
   * 每當源更改時自動從視頻元素中刪除
   * @return {HTMLTrackElement} 一個 Html 軌道元素。
   * 這可以是模擬的 {@link HTMLTrackElement} 或原生的。
   * @deprecated “manualCleanup”參數的默認值將默認
   * 在即將推出的 Video.js 版本中設置為“false”
   */
  addRemoteTextTrack(選項,manualCleanup){
    常量 htmlTrackElement = super.addRemoteTextTrack(選項,manualCleanup);

    如果(this.featuresNativeTextTracks){
      this.el().appendChild(htmlTrackElement);
    }

    返回 htmlTrackElement;
  }

  /**
   * 從 TextTrackList 對像中移除遠程 TextTrack
   *
   * @param {TextTrack} 軌道
   * 要移除的 `TextTrack` 對象
   */
  removeRemoteTextTrack(軌道){
    super.removeRemoteTextTrack(track);

    如果(this.featuresNativeTextTracks){
      const tracks = this.$$('track');

      設 i = tracks.length;

      當我 - ) {
        如果(軌道===軌道[i] ||軌道===軌道[i].軌道){
          this.el().removeChild(tracks[i]);
        }
      }
    }
  }

  /**
   * 獲取 W3C 媒體指定的可用媒體播放質量指標
   * 播放質量 API。
   *
   * @see [規範]{@link https://wicg.github.io/media-playback-quality}
   *
   * @return {對象}
   * 具有支持的媒體播放質量指標的對象
   */
  getVideoPlaybackQuality() {
    if (typeof this.el().getVideoPlaybackQuality === '函數') {
      返回 this.el().getVideoPlaybackQuality();
    }

    const videoPlaybackQuality = {};

    if (typeof this.el().webkitDroppedFrameCount !== 'undefined' &&
        typeof this.el().webkitDecodedFrameCount !== 'undefined') {
      videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
      videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
    }

    如果(window.performance && typeof window.performance.now === '函數'){
      videoPlaybackQuality.creationTime = window.performance.now();
    } else if (window.performance &&
               window.performance.timing &&
               typeof window.performance.timing.navigationStart === 'number') {
      videoPlaybackQuality.creationTime =
        window.Date.now() - window.performance.timing.navigationStart;
    }

    返回視頻播放質量;
  }
}

/* HTML5 支持測試 ------------------------------------------ ------ */

/**
 * 用於測試瀏覽器 HTML5 媒體功能的元素
 *
 * @type {元素}
 * @持續的
 * @私人的
 */
defineLazyProperty(Html5, 'TEST_VID', function() {
  如果(!Dom.isReal()){
    返回;
  }
  const video = document.createElement('video');
  const track = document.createElement('track');

  track.kind = '字幕';
  track.srclang = 'en';
  track.label = '英文';
  video.appendChild(軌道);

  返回視頻;
});

/**
 * 檢查此瀏覽器/設備是否支持 HTML5 媒體。
 *
 * @return {布爾值}
 * - 如果支持 HTML5 媒體則為真。
 * - 如果不支持 HTML5 媒體則為假。
 */
Html5.isSupported = function() {
  // 沒有媒體播放器的 IE 是騙子! (#984)
  嘗試{
    html5.TEST_VID.volume = 0.5;
  } 抓住 (e) {
    返回假;
  }

  返回!!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
};

/**
 * 檢查技術是否可以支持給定的類型
 *
 * @param {string} 類型
 * 要檢查的 mimetype
 * @return {string} 'probably', 'maybe', or '' (空字符串)
 */
Html5.canPlayType = 函數(類型){
  返回 Html5.TEST_VID.canPlayType(類型);
};

/**
 * 檢查技術是否可以支持給定的來源
 *
 * @param {對象} srcObj
 * 源對象
 * @param {Object} 選項
 * 傳遞給技術的選項
 * @return {string} 'probably', 'maybe', or '' (空字符串)
 */
Html5.canPlaySource = 函數(srcObj,選項){
  返回 Html5.canPlayType(srcObj.type);
};

/**
 * 檢查是否可以在此瀏覽器/設備中更改音量。
 * 很多移動設備無法更改音量。
 * 具體而言,在 iOS 上無法從 1 更改。
 *
 * @return {布爾值}
 * - 如果可以控制音量則為真
 * - 否則為假
 */
Html5.canControlVolume = function() {
  // 如果未安裝 Windows Media Player,IE 將出錯 #3315
  嘗試{
    const volume = Html5.TEST_VID.volume;

    html5.TEST_VID.volume = (volume / 2) + 0.1;

    const canControl = volume !== Html5.TEST_VID.volume;

    // 隨著 iOS 15 的引入,出現了將音量讀取為
    // 已更改但會在下一個滴答開始時恢復到原始狀態。
    //判斷音量是否可以在iOS上控制,
    // 設置超時並異步檢查音量。
    // 由於 `features` 當前不異步工作,因此該值是手動設置的。
    如果(canControl && browser.IS_IOS){
      window.setTimeout(() => {
        如果(Html5 && Html5.prototype){
          Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
        }
      });

      // 默認 iOS 為 false,將在上面的超時時間更新。
      返回假;
    }

    返回可以控制;
  } 抓住 (e) {
    返回假;
  }
};

/**
 * 檢查是否可以在此瀏覽器/設備中將音量靜音。
 * 某些設備,例如 iOS,不允許改變音量
 * 但允許靜音/取消靜音。
 *
 * @return {布爾值}
 * - 如果音量可以靜音則為真
 * - 否則為假
 */
Html5.canMuteVolume = function() {
  嘗試{
    const muted = Html5.TEST_VID.muted;

    // 在某些版本的 iOS 中,muted 屬性並不總是
    // 工作,所以我們想同時設置屬性和屬性
    Html5.TEST_VID.muted = !muted;
    如果 (Html5.TEST_VID.muted) {
      Dom.setAttribute(Html5.TEST_VID, 'muted', 'muted');
    }其他{
      Dom.removeAttribute(Html5.TEST_VID, 'muted', 'muted');
    }
    返回靜音 !== Html5.TEST_VID.muted;
  } 抓住 (e) {
    返回假;
  }
};

/**
 * 檢查是否可以在此瀏覽器/設備中更改播放速率。
 *
 * @return {布爾值}
 * - 如果可以控製播放速率則為真
 * - 否則為假
 */
Html5.canControlPlaybackRate = function() {
  // 播放速率 API 在 Android Chrome 中實現,但不做任何事情
  // https://github.com/videojs/video.js/issues/3180
  如果(瀏覽器.IS_ANDROID && 瀏覽器.IS_CHROME && 瀏覽器.CHROME_VERSION < 58){
    返回假;
  }
  // 如果未安裝 Windows Media Player,IE 將出錯 #3315
  嘗試{
    const playbackRate = Html5.TEST_VID.playbackRate;

    Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
    返回 playbackRate !== Html5.TEST_VID.playbackRate;
  } 抓住 (e) {
    返回假;
  }
};

/**
 * 檢查我們是否可以覆蓋視頻/音頻元素的屬性,
 * 對象.defineProperty。
 *
 * @return {布爾值}
 * - 如果可以覆蓋內置屬性則為真
 * - 否則為假
 */
Html5.canOverrideAttributes = function() {
  // 如果我們不能覆蓋 src/innerHTML 屬性,則不支持
  // 例如 iOS 7 safari 不能這樣做。
  嘗試{
    const noop = () => {};

    Object.defineProperty(document.createElement('video'), 'src', {get: noop, set: noop});
    Object.defineProperty(document.createElement('audio'), 'src', {get: noop, set: noop});
    Object.defineProperty(document.createElement('video'), 'innerHTML', {get: noop, set: noop});
    Object.defineProperty(document.createElement('audio'), 'innerHTML', {get: noop, set: noop});
  } 抓住 (e) {
    返回假;
  }

  返回真;
};

/**
 * 檢查此瀏覽器/設備是否支持本機 `TextTrack`。
 *
 * @return {布爾值}
 * - 如果支持原生 `TextTrack` 則為真。
 * - 否則為假
 */
Html5.supportsNativeTextTracks = function() {
  返回 browser.IS_ANY_SAFARI || (瀏覽器.IS_IOS && 瀏覽器.IS_CHROME);
};

/**
 * 檢查此瀏覽器/設備是否支持本機 `VideoTrack`
 *
 * @return {布爾值}
 * - 如果支持原生 `VideoTrack` 則為真。
 * - 否則為假
 */
Html5.supportsNativeVideoTracks = function() {
  返回 !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
};

/**
 * 檢查此瀏覽器/設備是否支持本機 `AudioTrack`
 *
 * @return {布爾值}
 * - 如果支持原生 `AudioTrack` 則為真。
 * - 否則為假
 */
Html5.supportsNativeAudioTracks = function() {
  返回!!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
};

/**
 * Html5 技術上可用的一系列事件。
 *
 * @私人的
 * @type {數組}
 */
Html5.事件 = [
  '加載開始',
  '暫停',
  '中止',
  '錯誤',
  '清空',
  '停滯不前',
  '加載元數據',
  '加載數據',
  '可以玩',
  '可以通過',
  '玩',
  '等待',
  '尋求',
  '尋求',
  '結束',
  '持續時間變化',
  '時間更新',
  '進步',
  '玩',
  '暫停',
  '匯率變化',
  '調整大小',
  '體積變化'
];

/**
 * 布爾值表示 `Tech` 是否支持音量控制。
 *
 * @type {布爾}
 * @default {@link Html5.canControlVolume}
 */
/**
 * 表示 `Tech` 是否支持靜音音量的布爾值。
 *
 * @type {布爾值}
 * @default {@link Html5.canMuteVolume}
 */

/**
 * 表示 `Tech` 是否支持改變媒體播放速度的布爾值
 * 播放。範例:
 * - 設置播放器以 2 倍(兩倍)的速度播放
 * - 設置播放器播放速度為 0.5 倍(一半)
 *
 * @type {布爾}
 * @default {@link Html5.canControlPlaybackRate}
 */

/**
 * 表示 `Tech` 是否支持 `sourceset` 事件的布爾值。
 *
 * @type {布爾}
 * @默認
 */
/**
 * 表示 `HTML5` 技術當前是否支持原生 `TextTrack` 的布爾值。
 *
 * @type {布爾}
 * @default {@link Html5.supportsNativeTextTracks}
 */
/**
 * 表示 `HTML5` 技術當前是否支持原生 `VideoTrack` 的布爾值。
 *
 * @type {布爾}
 * @default {@link Html5.supportsNativeVideoTracks}
 */
/**
 * 表示 `HTML5` 技術當前是否支持原生 `AudioTrack` 的布爾值。
 *
 * @type {布爾}
 * @default {@link Html5.supportsNativeAudioTracks}
 */
[
  ['featuresMuteControl', 'canMuteVolume'],
  ['featuresPlaybackRate', 'canControlPlaybackRate'],
  ['featuresSourceset', 'canOverrideAttributes'],
  ['featuresNativeTextTracks', 'supportsNativeTextTracks'],
  ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'],
  ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']
].forEach(函數([key, fn]) {
  defineLazyProperty(Html5.prototype, key, () => Html5[fn](), true);
});

Html5.prototype.featuresVolumeControl = Html5.canControlVolume();

/**
 * 布爾值,指示 `HTML5` 技術當前是否支持媒體元素
 * 在 DOM 中移動。如果移動媒體元素,iOS 會中斷,因此將其設置為
 * 假的。這在其他任何地方都應該是正確的。
 *
 * @type {布爾}
 * @默認
 */
Html5.prototype.movingMediaElementInDOM = !browser.IS_IOS;

// 去做:上一條評論:似乎不再使用。大概可以去掉。
// 這是真的?
/**
 * 表示 `HTML5` 技術當前是否支持自動調整媒體大小的布爾值
 * 進入全屏時。
 *
 * @type {布爾}
 * @默認
 */
Html5.prototype.featuresFullscreenResize = true;

/**
 * 表示 `HTML5` 技術當前是否支持進度事件的布爾值。
 * 如果這是 false,將改為觸發手動“progress”事件。
 *
 * @type {布爾}
 * @默認
 */
Html5.prototype.featuresProgressEvents = true;

/**
 * 表示 `HTML5` 技術當前是否支持 timeupdate 事件的布爾值。
 * 如果這是 false,將改為觸發手動 `timeupdate` 事件。
 *
 * @默認
 */
Html5.prototype.featuresTimeupdateEvents = true;

/**
 * HTML5 el是否支持`requestVideoFrameCallback`
 *
 * @type {布爾}
 */
Html5.prototype.featuresVideoFrameCallback = !!(Html5.TEST_VID && Html5.TEST_VID.requestVideoFrameCallback);

// HTML5 功能檢測和設備修復 ---------------------------------- //
讓 canPlayType;

Html5.patchCanPlayType = function() {

  // Android 4.0及以上版本可以一定程度播放HLS但報無法播放
  // Firefox 和 Chrome 正確報告
  如果(瀏覽器.ANDROID_VERSION >= 4.0 && !browser.IS_FIREFOX && !browser.IS_CHROME){
    canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
    Html5.TEST_VID.constructor.prototype.canPlayType = 函數(類型){
      const mpegurlRE = /^application\\/(?:x-|vnd\\.apple\\.)mpegurl/i;

      如果(類型 && mpegurlRE.test(類型)){
        返回“也許”;
      }
      返回 canPlayType.call(this, type);
    };
  }
};

Html5.unpatchCanPlayType = function() {
  const r = Html5.TEST_VID.constructor.prototype.canPlayType;

  如果(canPlayType){
    Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
  }
  返回 r;
};

// 默認情況下,修補媒體元素
Html5.patchCanPlayType();

Html5.disposeMediaElement = function(el) {
  如果(!el){
    返回;
  }

  如果(el.parentNode){
    el.parentNode.removeChild(el);
  }

  // 刪除任何子軌道或源節點以防止加載它們
  while (el.hasChildNodes()) {
    el.removeChild(el.firstChild);
  }

  // 刪除任何 src 引用。不設置 `src=''` 因為這會導致警告
  // 在火狐中
  el.removeAttribute('src');

  // 通過調用 load() 強制媒體元素更新其加載狀態
  // 但是 Windows 7N 上的 IE 有一個會引發錯誤的錯誤,因此需要一個 try/catch (#793)
  如果 (typeof el.load === '函數') {
    // 包裝在一個 iife 中,這樣它就不會被取消優化 (#1060#discussion_r10324473)
    (功能() {
      嘗試{
        el.load();
      } 抓住 (e) {
        // 不支持
      }
    }());
  }
};

Html5.resetMediaElement = function(el) {
  如果(!el){
    返回;
  }

  const sources = el.querySelectorAll('source');
  讓我=來源.長度;

  當我 - ) {
    el.removeChild(來源[i]);
  }

  // 刪除任何 src 引用。
  // 不設置 `src=''` 因為那會拋出錯誤
  el.removeAttribute('src');

  如果 (typeof el.load === '函數') {
    // 包裝在一個 iife 中,這樣它就不會被取消優化 (#1060#discussion_r10324473)
    (功能() {
      嘗試{
        el.load();
      } 抓住 (e) {
        // 滿足 linter
      }
    }());
  }
};

/* 原生 HTML5 元素屬性包裝 ---------------------------------- */
// 使用同時檢查屬性和屬性的 getter 包裝本機布爾屬性
// 列表如下:
// 靜音,默認靜音,自動播放,控制,循環,在線播放
[
  /**
   * 從媒體元素中獲取 `muted` 的值。 `muted` 表示
   * 媒體音量應設置為靜音。這實際上並沒有改變
   * `volume` 屬性。
   *
   * @method Html5#靜音
   * @return {布爾值}
   * - 如果應忽略 `volume` 的值並將音頻設置為靜音,則為 True。
   * - 如果應該使用 `volume` 的值則為 false。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
   */
  '靜音',

  /**
   * 從媒體元素中獲取 `defaultMuted` 的值。 `defaultMuted` 表示
   *媒體是否應該開始靜音。只改變默認狀態
   * 媒體。 `muted` 和 `defaultMuted` 可以有不同的值。 {@link Html5#muted} 表示
   * 當前狀態。
   *
   * @method Html5#defaultMuted
   * @return {布爾值}
   * - 媒體元素中“defaultMuted”的值。
   * - True 表示媒體應該開始靜音。
   * - False 表示媒體不應該開始靜音
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
   */
  '默認靜音',

  /**
   * 從媒體元素中獲取 `autoplay` 的值。 `自動播放`表示
   * 媒體應該在頁面準備好後立即開始播放。
   *
   * @method Html5#自動播放
   * @return {布爾值}
   * - 媒體元素中“autoplay”的值。
   * - True 表示媒體應在頁面加載後立即啟動。
   * - False 表示媒體不應在頁面加載後立即啟動。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
   */
  '自動播放',

  /**
   * 從媒體元素中獲取 `controls` 的值。 `controls` 表示
   * 本機媒體控件是否應顯示或隱藏。
   *
   * @method Html5#控件
   * @return {布爾值}
   * - 媒體元素中“controls”的值。
   * - True 表示應顯示本機控件。
   * - False 表示應隱藏本機控件。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
   */
  “控制”,

  /**
   * 從媒體元素中獲取 `loop` 的值。 `loop` 表示
   *媒體應該回到媒體的開始並繼續播放一次
   * 它到達終點。
   *
   * @method Html5#循環
   * @return {布爾值}
   * - 媒體元素中 `loop` 的值。
   * - True 表示播放應該重新開始一次
   * 到達媒體的結尾。
   * - False 表示當
   * 到達媒體末尾。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
   */
  '環形',

  /**
   * 從媒體元素中獲取 `playsinline` 的值。 `playsinline` 表示
   * 瀏覽器在全屏時首選非全屏播放
   * 播放是本機默認設置,例如在 iOS Safari 中。
   *
   * @method Html5#playsinline
   * @return {布爾值}
   * - 媒體元素中 `playsinline` 的值。
   * - True 表示媒體應該內聯播放。
   * - False 表示媒體不應內聯播放。
   *
   * @see [規範]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
   */
  '在線播放'
].forEach(函數(道具){
  Html5.prototype[prop] = function() {
    返回 this.el_[prop] || this.el_.hasAttribute(prop);
  };
});

// 使用設置屬性和屬性的設置器包裝本機布爾屬性
// 列表如下:
// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
// setControls 是上面的特例
[
  /**
   * 在媒體元素上設置 `muted` 的值。 `muted` 表示當前
   * 音量應該是無聲的。
   *
   * @method Html5#setMuted
   * @param {boolean} 靜音
   * - 如果音頻應設置為靜音則為真
   * - 否則為假
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
   */
  '靜音',

  /**
   * 在媒體元素上設置 `defaultMuted` 的值。 `defaultMuted` 表示當前
   * 音頻級別應該是無聲的,但只會影響初始播放時的靜音級別。
   *
   * @method Html5.prototype.setDefaultMuted
   * @param {boolean} defaultMuted
   * - 如果音頻應設置為靜音則為真
   * - 否則為假
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
   */
  '默認靜音',

  /**
   * 在媒體元素上設置 `autoplay` 的值。 `自動播放`表示
   * 媒體應該在頁面準備好後立即開始播放。
   *
   * @method Html5#setAutoplay
   * @param {boolean} 自動播放
   * - True 表示媒體應在頁面加載後立即啟動。
   * - False 表示媒體不應在頁面加載後立即啟動。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
   */
  '自動播放',

  /**
   * 在媒體元素上設置 `loop` 的值。 `loop` 表示
   *媒體應該回到媒體的開始並繼續播放一次
   * 它到達終點。
   *
   * @method Html5#setLoop
   * @param {boolean} 循環
   * - True 表示播放應該重新開始一次
   * 到達媒體的結尾。
   * - False 表示當
   * 到達媒體末尾。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
   */
  '環形',

  /**
   * 從媒體元素中設置 `playsinline` 的值。 `playsinline` 表示
   * 瀏覽器在全屏時首選非全屏播放
   * 播放是本機默認設置,例如在 iOS Safari 中。
   *
   * @method Html5#setPlaysinline
   * @param {boolean} 在線播放
   * - True 表示媒體應該內聯播放。
   * - False 表示媒體不應內聯播放。
   *
   * @see [規範]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
   */
  '在線播放'
].forEach(函數(道具){
  Html5.prototype['set' + toTitleCase(prop)] = function(v) {
    this.el_[prop] = v;

    如果(五){
      this.el_.setAttribute(prop, prop);
    }其他{
      this.el_.removeAttribute(prop);
    }
  };
});

// 用 getter 包裝原生屬性
// 列表如下
// 暫停、當前時間、緩衝、音量、海報、預加載、錯誤、搜索
// seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture
// played, networkState, readyState, videoWidth, videoHeight, crossOrigin
[
  /**
   * 從媒體元素中獲取 `paused` 的值。 `paused` 表示媒體元素是否
   * 當前是否暫停。
   *
   * @method Html5#暫停
   * @return {布爾值}
   * 媒體元素中 `paused` 的值。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
   */
  '暫停',

  /**
   * 從媒體元素中獲取 `currentTime` 的值。 `currentTime` 表示
   * 媒體播放的當前秒數。
   *
   * @method Html5#currentTime
   * @return {數字}
   * 來自媒體元素的 `currentTime` 的值。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
   */
  '當前時間',

  /**
   * 從媒體元素中獲取 `buffered` 的值。 `buffered` 是一個 `TimeRange`
   * 表示已經下載的媒體部分的對象
   * 可用於播放。
   *
   * @method Html5#buffered
   * @return {TimeRange}
   * 來自媒體元素的 `buffered` 的值。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
   */
  '緩衝的',

  /**
   * 從媒體元素中獲取 `volume` 的值。 `volume`表示
   * 媒體音頻的當前播放音量。 `volume` 將是一個從 0 開始的值
   *(靜音)到 1(最大聲和默認)。
   *
   * @method Html5#volume
   * @return {數字}
   * 來自媒體元素的 `volume` 的值。值將在 0-1 之間。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
   */
  '體積',

  /**
   * 從媒體元素中獲取 `poster` 的值。 `海報`表示
   * 當沒有媒體數據可用時可以/將顯示的圖像文件的 url。
   *
   * @method Html5#海報
   * @return {字符串}
   * 來自媒體元素的 `poster` 的值。值將是一個 url
   * 圖像。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
   */
  '海報',

  /**
   * 從媒體元素中獲取 `preload` 的值。 `preload` 表示
   * 在與媒體交互之前應該下載什麼。它可以有以下內容
   * 值:
   * - 無:不應下載任何內容
   * - 元數據:海報和媒體的前幾幀可以下載以獲得
   * 媒體維度和其他元數據
   * - 自動:允許媒體和媒體的元數據在之前下載
   * 相互作用
   *
   * @method Html5#預加載
   * @return {字符串}
   * 來自媒體元素的 `preload` 的值。將是“無”,“元數據”,
   * 或“自動”。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
   */
  '預加載',

  /**
   * 從媒體元素中獲取 `error` 的值。 `error` 表示任何
   * 播放過程中可能發生的 MediaError。如果錯誤返回 null 則沒有
   * 當前錯誤。
   *
   * @method Html5#錯誤
   * @return {MediaError|null}
   * 來自媒體元素的 `error` 的值。如果存在,將是 MediaError
   * 是當前錯誤,否則為空。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
   */
  '錯誤',

  /**
   * 從媒體元素中獲取 `seeking` 的值。 `seeking` 表示是否
   * 媒體目前正在尋求一個新的位置與否。
   *
   * @method Html5#seeking
   * @return {布爾值}
   * - 從媒體元素中尋找的值。
   * - True 表示媒體當前正在尋找新位置。
   * - False 表示媒體目前沒有尋求新的立場。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
   */
  '尋求',

  /**
   * 從媒體元素中獲取 `seekable` 的值。 `seekable` 返回一個
   * `TimeRange` 對象指示當前可以“搜索”到的時間範圍。
   *
   * @method Html5#seekable
   * @return {TimeRange}
   * media 元素中 `seekable` 的值。一個 TimeRange 對象
   * 指示當前可以搜索到的時間範圍。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
   */
  '可尋',

  /**
   * 從媒體元素中獲取 `ended` 的值。 `ended` 表示是否
   * 媒體是否到達盡頭。
   *
   * @method Html5#ended
   * @return {布爾值}
   * - 來自媒體元素的 `ended` 的值。
   * - True 表示媒體已經結束。
   * - False 表示媒體尚未結束。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
   */
  '結束',

  /**
   * 從媒體元素中獲取 `playbackRate` 的值。 `playbackRate` 表示
   * 媒體當前播放的速率。範例:
   * - 如果 playbackRate 設置為 2,媒體將以兩倍的速度播放。
   * - 如果 playbackRate 設置為 0.5,媒體播放速度將減半。
   *
   * @method Html5#playbackRate
   * @return {數字}
   * 媒體元素中 `playbackRate` 的值。一個數字表示
   * 媒體的當前播放速度,其中 1 是正常速度。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
   */
  '播放率',

  /**
   * 從媒體元素中獲取 `defaultPlaybackRate` 的值。 `defaultPlaybackRate` 表示
   * 媒體當前播放的速率。該值將不表示當前
   * 播放開始後的 `playbackRate`,為此使用 {@link Html5#playbackRate}。
   *
   * 例子:
   * - 如果 defaultPlaybackRate 設置為 2,媒體將以兩倍的速度播放。
   * - 如果 defaultPlaybackRate 設置為 0.5,媒體播放速度將減半。
   *
   * @method Html5.prototype.defaultPlaybackRate
   * @return {數字}
   * 來自媒體元素的 `defaultPlaybackRate` 的值。一個數字表示
   * 媒體的當前播放速度,其中 1 是正常速度。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
   */
  '默認播放率',

  /**
   * 從視頻元素中獲取 'disablePictureInPicture' 的值。
   *
   * @method Html5#disablePictureInPicture
   * @return {boolean} 值
   * - video 元素中 `disablePictureInPicture` 的值。
   * - True 表示視頻無法以畫中畫模式播放
   * - false 表示視頻可以畫中畫模式播放
   *
   * @see [規範]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
   */
  '禁用畫中畫',

  /**
   * 從媒體元素中獲取 `played` 的值。 `played` 返回一個 `TimeRange`
   * 表示媒體時間軸中已播放點的對象。
   *
   * @method Html5#播放
   * @return {TimeRange}
   * 媒體元素中“played”的值。一個 TimeRange 對象表示
   * 已播放的時間範圍。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
   */
  '播放',

  /**
   * 從媒體元素中獲取 `networkState` 的值。 `networkState` 表示
   * 當前網絡狀態。它從以下列表返回一個枚舉:
   * - 0:NETWORK_EMPTY
   * - 1:網絡空閒
   * - 2:網絡加載
   * - 3:NETWORK_NO_SOURCE
   *
   * @method Html5#networkState
   * @return {數字}
   * 來自媒體元素的 `networkState` 的值。這將是一個數字
   * 來自描述中的列表。
   *
   * @see [規範] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
   */
  '網絡狀態',

  /**
   * 從媒體元素中獲取 readyState 的值。 `readyState` 表示
   * 媒體元素的當前狀態。它返回一個枚舉
   * 以下列表:
   * - 0:一無所有
   * - 1:有_元數據
   * - 2:有_CURRENT_DATA
   * - 3:有_FUTURE_DATA
   * - 4:有_ENOUGH_DATA
   *
   * @method Html5#readyState
   * @return {數字}
   * 來自媒體元素的 `readyState` 的值。這將是一個數字
   * 來自描述中的列表。
   *
   * @see [規範] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
   */
  '就緒狀態',

  /**
   * 從視頻元素中獲取 `videoWidth` 的值。 `videoWidth`表示
   * 視頻的當前寬度(以 css 像素為單位)。
   *
   * @method Html5#videoWidth
   * @return {數字}
   * 來自視頻元素的 `videoWidth` 的值。這將是一個數字
   * 以 css 像素為單位。
   *
   * @see [規範] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
   */
  '視頻寬度',

  /**
   * 從視頻元素中獲取 `videoHeight` 的值。 `videoHeight`表示
   * 視頻的當前高度(以 css 像素為單位)。
   *
   * @method Html5#videoHeight
   * @return {數字}
   * video 元素中 `videoHeight` 的值。這將是一個數字
   * 以 css 像素為單位。
   *
   * @see [規範] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
   */
  '視頻高度',

  /**
   * 從媒體元素中獲取 `crossOrigin` 的值。 `crossOrigin` 表示
   * 發送給應該連同請求一起發送 cookie 的瀏覽器
   * 不同的資產/播放列表
   *
   * @method Html5#crossOrigin
   * @return {字符串}
   * - 匿名表示媒體不應發送 cookie。
   * - use-credentials 表示媒體應隨請求發送 cookie。
   *
   * @see [規範]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
   */
  '跨域'
].forEach(函數(道具){
  Html5.prototype[prop] = function() {
    返回 this.el_[prop];
  };
});

// 以此格式使用 setter 包裝本機屬性:
// 設置 + toTitleCase(名稱)
// 列表如下:
// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate,
// setDisablePictureInPicture, setCrossOrigin
[
  /**
   * 在媒體元素上設置 `volume` 的值。 `volume`表示當前
   * 以小數形式表示的音頻電平百分比。這意味著 1 是 100%,0.5 是 50%,而
   * 很快。
   *
   * @method Html5#setVolume
   * @param {number} percentAsDecimal
   * 小數形式的體積百分比。有效範圍為 0-1。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
   */
  '體積',

  /**
   * 在媒體元素上設置 `src` 的值。 `src`表示當前
   * {@link Tech~SourceObject} 媒體。
   *
   * @method Html5#setSrc
   * @param {Tech~SourceObject} 源
   * 要設置為當前源的源對象。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
   */
  '來源',

  /**
   * 在媒體元素上設置 `poster` 的值。 `poster` 是網址
   * 當沒有媒體數據可用時可以/將顯示的圖像文件。
   *
   * @method Html5#setPoster
   * @param {string} 海報
   * 應該用作媒體“海報”的圖像的 url
   * 元素。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
   */
  '海報',

  /**
   * 在媒體元素上設置 `preload` 的值。 `preload` 表示
   * 在與媒體交互之前應該下載什麼。它可以有以下內容
   * 值:
   * - 無:不應下載任何內容
   * - 元數據:海報和媒體的前幾幀可以下載以獲得
   * 媒體維度和其他元數據
   * - 自動:允許媒體和媒體的元數據在之前下載
   * 相互作用
   *
   * @method Html5#setPreload
   * @param {string} 預加載
   * 在媒體元素上設置的 `preload` 的值。必須是“無”,“元數據”,
   * 或“自動”。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
   */
  '預加載',

  /**
   * 在媒體元素上設置 `playbackRate` 的值。 `playbackRate` 表示
   * 媒體播放的速率。範例:
   * - 如果 playbackRate 設置為 2,媒體將以兩倍的速度播放。
   * - 如果 playbackRate 設置為 0.5,媒體播放速度將減半。
   *
   * @method Html5#setPlaybackRate
   * @return {數字}
   * 媒體元素中 `playbackRate` 的值。一個數字表示
   * 媒體的當前播放速度,其中 1 是正常速度。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
   */
  '播放率',

  /**
   * 在媒體元素上設置 `defaultPlaybackRate` 的值。 `defaultPlaybackRate` 表示
   * 媒體在初始啟動時應播放的速率。改變這個值
   * 視頻開始後將不執行任何操作。相反,您應該使用 {@link Html5#setPlaybackRate}。
   *
   * 示例值:
   * - 如果 playbackRate 設置為 2,媒體將以兩倍的速度播放。
   * - 如果 playbackRate 設置為 0.5,媒體播放速度將減半。
   *
   * @method Html5.prototype.setDefaultPlaybackRate
   * @return {數字}
   * 來自媒體元素的 `defaultPlaybackRate` 的值。一個數字表示
   * 媒體的當前播放速度,其中 1 是正常速度。
   *
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
   */
  '默認播放率',

  /**
   * 防止瀏覽器建議畫中畫上下文菜單
   * 或在某些情況下自動請求畫中畫。
   *
   * @method Html5#setDisablePictureInPicture
   * @param {boolean} 值
   * 真值將禁用畫中畫模式。
   *
   * @see [規範]{@link https://w3c.github.io/picture-in-picture/#disable-pip}
   */
  '禁用畫中畫',

  /**
   * 從媒體元素設置 `crossOrigin` 的值。 `crossOrigin` 表示
   * 發送給應該連同請求一起發送 cookie 的瀏覽器
   * 不同的資產/播放列表
   *
   * @method Html5#setCrossOrigin
   * @param {string} 跨域
   * - 匿名表示媒體不應發送 cookie。
   * - use-credentials 表示媒體應隨請求發送 cookie。
   *
   * @see [規範]{@link https://html.spec.whatwg.org/#attr-media-crossorigin}
   */
  '跨域'
].forEach(函數(道具){
  Html5.prototype['set' + toTitleCase(prop)] = function(v) {
    this.el_[prop] = v;
  };
});

// 用函數包裝原生函數
// 列表如下:
// 暫停、加載、播放
[
  /**
   * 媒體元素“暫停”功能的包裝器。這將調用 `HTML5`
   * 媒體元素`暫停`功能。
   *
   * @method Html5#暫停
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
   */
  '暫停',

  /**
   * 媒體元素 `load` 函數的包裝器。這將調用`HTML5`s
   * 媒體元素 `load` 函數。
   *
   * @method Html5#load
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
   */
  '加載',

  /**
   * 媒體元素“播放”功能的包裝器。這將調用`HTML5`s
   * 媒體元素“播放”功能。
   *
   * @method Html5#播放
   * @see [規範]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
   */
  '玩'
].forEach(函數(道具){
  Html5.prototype[prop] = function() {
    返回 this.el_[prop]();
  };
});

Tech.withSourceHandlers(Html5);

/**
 * Html5 的本機源處理程序,只需將源傳遞給媒體元素。
 *
 * @property {Tech~SourceObject} 來源
 * 源對象
 *
 * @property {Html5} 技術
 * HTML5 技術實例。
 */
Html5.nativeSourceHandler = {};

/**
 * 檢查媒體元素是否可以播放給定的 MIME 類型。
 *
 * @param {string} 類型
 * 要檢查的 mimetype
 *
 * @return {字符串}
 *“可能”、“也許”或“”(空字符串)
 */
Html5.nativeSourceHandler.canPlayType = 函數(類型){
  // 沒有 MediaPlayer 的 IE 會拋出錯誤 (#519)
  嘗試{
    返回 Html5.TEST_VID.canPlayType(類型);
  } 抓住 (e) {
    返回 '';
  }
};

/**
 * 檢查媒體元素是否可以本地處理源。
 *
 * @param {Tech~SourceObject} 來源
 * 源對象
 *
 * @param {對象} [選項]
 * 傳遞給技術的選項。
 *
 * @return {字符串}
 *“可能”、“也許”或“”(空字符串)。
 */
Html5.nativeSourceHandler.canHandleSource = 函數(來源,選項){

  // 如果提供了類型,我們應該依賴它
  如果(來源。類型){
    返回 Html5.nativeSourceHandler.canPlayType(source.type);

  // 如果沒有類型,回退到檢查 'video/[EXTENSION]'
  } else if (source.src) {
    const ext = Url.getFileExtension(source.src);

    返回 Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
  }

  返回 '';
};

/**
 * 將源傳遞給原生媒體元素。
 *
 * @param {Tech~SourceObject} 來源
 * 源對象
 *
 * @param {Html5} 技術
 * Html5技術實例
 *
 * @param {對象} [選項]
 * 傳遞給源的選項
 */
Html5.nativeSourceHandler.handleSource = function(source, tech, options) {
  tech.setSrc(source.src);
};

/**
 * 本機處理函數的 noop,因為不需要清理。
 */
Html5.nativeSourceHandler.dispose = function() {};

// 註冊本機源處理程序
Html5.registerSourceHandler(Html5.nativeSourceHandler);

Tech.registerTech('Html5', Html5);
導出默認的 Html5;