/**
 * @file plugin.js
 */
從'./mixins/evented'導入事件;
從“./mixins/stateful”導入有狀態;
從“./utils/events”導入 * 作為事件;
從“./utils/log”導入日誌;
從'./player'導入播放器;

/**
 * 基本插件名稱。
 *
 * @私人的
 * @持續的
 * @type {字符串}
 */
const BASE_PLUGIN_NAME = '插件';

/**
 * 存儲播放器的活動插件緩存的密鑰。
 *
 * @私人的
 * @持續的
 * @type {字符串}
 */
const PLUGIN_CACHE_KEY = 'activePlugins_';

/**
 * 將已註冊的插件存儲在私人空間中。
 *
 * @私人的
 * @type {對象}
 */
const pluginStorage = {};

/**
 * 報告插件是否已註冊。
 *
 * @私人的
 * @param {string} 名稱
 * 插件名稱。
 *
 * @return {布爾值}
 * 插件是否已經註冊。
 */
const pluginExists = (name) => pluginStorage.hasOwnProperty(name);

/**
 * 按名稱獲取單個註冊插件。
 *
 * @私人的
 * @param {string} 名稱
 * 插件名稱。
 *
 * @return {函數|未定義}
 * 插件(或未定義)。
 */
常量獲取插件 =(名稱)=> 插件存在(名稱)?插件風暴 [名稱]:未定義;

/**
 * 在播放器上將插件標記為“活動”。
 *
 * 此外,確保播放器具有用於跟踪活動插件的對象。
 *
 * @私人的
 * @param {Player} 播放器
 * Video.js 播放器實例。
 *
 * @param {string} 名稱
 * 插件名稱。
 */
const markPluginAsActive = (player, name) => {
  播放器[PLUGIN_CACHE_KEY] = 播放器[PLUGIN_CACHE_KEY] || {};
  播放器[PLUGIN_CACHE_KEY][名稱] = true;
};

/**
 * 觸發一對插件設置事件。
 *
 * @私人的
 * @param {Player} 播放器
 * Video.js 播放器實例。
 *
 * @param {Plugin~PluginEventHash} 散列
 * 插件事件哈希。
 *
 * @param {boolean} [之前]
 * 如果為真,則在事件名稱前加上“before”前綴。換句話說,
 * 使用它來觸發“beforepluginsetup”而不是“pluginsetup”。
 */
const triggerSetupEvent = (player, hash, before) => {
  常量事件名稱 =(之前?'之前':「)+ '插件設置';

  player.trigger(eventName, hash);
  player.trigger(eventName + ':' + hash.name, hash);
};

/**
 * 採用一個基本的插件函數並返回一個包裝函數,它標記
 * 在插件已激活的播放器上。
 *
 * @私人的
 * @param {string} 名稱
 * 插件名稱。
 *
 * @param {函數} 插件
 * 基本插件。
 *
 * @return {函數}
 * 給定插件的包裝函數。
 */
const createBasicPlugin = function(名稱, 插件) {
  const basicPluginWrapper = function() {

    // 我們在播放器上觸發“beforepluginsetup”和“pluginsetup”事件
    // 不管怎樣,但我們希望散列值與提供的散列值一致
    // 對於高級插件。
    //
    // 這裡唯一可能違反直覺的是 `instance`
    // "pluginsetup" 事件是 `plugin` 函數返回的值。
    triggerSetupEvent(this, {name, plugin, instance: null}, true);

    const instance = plugin.apply(this, arguments);

    markPluginAsActive(這個,名字);
    triggerSetupEvent(this, {name, plugin, instance});

    返回實例;
  };

  Object.keys(插件).forEach(函數(prop) {
    basicPluginWrapper[prop] = 插件[prop];
  });

  返回 basicPluginWrapper;
};

/**
 * 採用插件子類並返回用於生成的工廠函數
 * 它的實例。
 *
 * 這個工廠函數將自己替換為請求的實例
 * 插件的子類。
 *
 * @私人的
 * @param {string} 名稱
 * 插件名稱。
 *
 * @param {Plugin} PluginSubClass
 * 高級插件。
 *
 * @return {函數}
 */
const createPluginFactory = (name, PluginSubClass) => {

  // 給插件原型添加一個 `name` 屬性,這樣每個插件都可以
  // 通過名稱引用自身。
  PluginSubClass.prototype.name = 名稱;

  返回函數(...參數){
    triggerSetupEvent(這個,{名稱,插件:PluginSubClass, 實例: null}, true);

    常量實例 = 新的插件子類(...[這,... 參數]);

    // 插件替換為返回當前實例的函數。
    這個[名稱] = () => 實例;

    triggerSetupEvent(this, instance.getEventHash());

    返回實例;
  };
};

/**
 * 所有高級插件的父類。
 *
 * @mixes 模塊:事件〜事件混入
 * @mixes 模塊:stateful~StatefulMixin
 * @fires Player#beforepluginsetup
 * @fires 播放器 #beforepluginsetup: $ 名稱
 * @fires Player#pluginsetup
 * @fires 播放器 #pluginsetup: $ 名稱
 * @listens Player#dispose
 * @throws {錯誤}
 * 如果試圖實例化基類 {@link Plugin}
 * 直接而不是通過子類。
 */
類插件{

  /**
   * 創建此類的一個實例。
   *
   * 子類應該調用 `super` 以確保插件被正確初始化。
   *
   * @param {Player} 播放器
   * Video.js 播放器實例。
   */
  構造函數(玩家){
    如果(this.constructor === 插件){
      throw new Error('插件必須被子類化;不能直接實例化。');
    }

    this.player = 播放器;

    如果(!this.log){
      this.log = this.player.log.createLogger(this.name);
    }

    // 使這個對象事件化,但刪除添加的 `trigger` 方法,這樣我們
    // 改用原型版本。
    事件(這);
    刪除這個。觸發器;

    有狀態的(這,this.constructor.defaultState);
    markPluginAsActive(播放器, this.name);

    // 自動綁定 dispose 方法,以便我們可以將其用作偵聽器並取消綁定
    // 以後很容易。
    this.dispose = this.dispose.bind(this);

    // 如果播放器被處置,則處置插件。
    player.on('dispose', this.dispose);
  }

  /**
   * 獲取在 <pluginName>.VERSION 上設置的插件版本
   */
  版本() {
    返回 this.constructor.VERSION;
  }

  /**
   * 插件觸發的每個事件都包含附加數據的哈希值
   * 常規屬性。
   *
   * 這將返回該對像或改變現有哈希。
   *
   * @param {對象} [散列={}]
   * 用作事件哈希的對象。
   *
   * @return {插件~PluginEventHash}
   * 混合了提供的屬性的事件哈希對象。
   */
  getEventHash(散列 = {}) {
    hash.name = this.name;
    hash.plugin = this.constructor;
    hash.instance = this;
    返回散列;
  }

  /**
   * 在插件對像上觸發事件並覆蓋
   * {@link 模塊:事件〜事件混合。觸發器 | 事件混合。觸發}。
   *
   * @param {string|Object} 事件
   * 事件類型或具有類型屬性的對象。
   *
   * @param {對象} [散列={}]
   * 附加數據散列與
   * {@link Plugin~PluginEventHash|PluginEventHash}。
   *
   * @return {布爾值}
   * 是否防止違約。
   */
  觸發器(事件,哈希={}){
    返回 Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
  }

  /**
   * 處理插件上的“狀態改變”事件。默認情況下無操作,由
   * 子類化。
   *
   * @抽象的
   * @param {事件} e
   * 由“statechanged”事件提供的事件對象。
   *
   * @param {Object} e.changes
   * 一個描述“狀態改變”發生的變化的對象
   * 事件。
   */
  handleStateChanged(e) {}

  /**
   * 配置一個插件。
   *
   * 子類可以根據需要覆蓋它,但為了安全起見,
   * 最好訂閱“dispose”事件。
   *
   * @fires 插件#dispose
   */
  處置(){
    const {name, player} = this;

    /**
     * 高級插件即將被處理的信號。
     *
     * @event 插件#dispose
     * @type {EventTarget~Event}
     */
    this.trigger('處置');
    這個。關閉();
    player.off('dispose', this.dispose);

    // 通過清理消除任何可能的內存洩漏源
    // 播放器和插件實例之間的引用並清空
    // 插件的狀態並用拋出的函數替換方法。
    播放器[PLUGIN_CACHE_KEY][名稱] = false;
    this.player = this.state = null;

    // 最後,用新工廠替換播放器上的插件名稱
    // 函數,以便插件準備好再次設置。
    player[name] = createPluginFactory(name, pluginStorage[name]);
  }

  /**
   * 確定插件是否是基本插件(即不是 `Plugin` 的子類)。
   *
   * @param {string|Function} 插件
   * 如果是字符串,則匹配插件的名稱。如果一個函數,將是
   * 直接測試。
   *
   * @return {布爾值}
   * 插件是否是基本插件。
   */
  靜態 isBasic(插件){
    常量 p =(插件 === '字符串' 的類型)?獲取插件(插件):插件;

    返回 p === '函數' &&!外掛程式原型. 原型 (p.原型);
  }

  /**
   * 註冊一個 Video.js 插件。
   *
   * @param {string} 名稱
   * 要註冊的插件名稱。必須是一個字符串並且
   * 不得與 `Player` 上的現有插件或方法匹配
   * 原型。
   *
   * @param {函數} 插件
   * `Plugin` 的子類或基本插件的函數。
   *
   * @return {函數}
   * 對於高級插件,該插件的工廠函數。為了
   * 基本插件,一個初始化插件的包裝函數。
   */
  靜態註冊插件(名稱,插件){
    如果(類型名稱!=='字符串'){
      throw new Error(`非法插件名稱,“${name}”,必須是一個字符串,是 ${typeof name}。`);
    }

    如果(插件存在(名稱)){
      log.warn(`名為“${name}”的插件已經存在。您可能希望避免重新註冊插件!`);
    } else if (Player.prototype.hasOwnProperty(name)) {
      throw new Error(`非法插件名稱,“${name}”,不能與現有播放器方法共享名稱!`);
    }

    如果(插件的類型!== '功能') {
      throw new Error(`“${name}”的非法插件,必須是函數,是 ${typeof plugin}。`);
    }

    pluginStorage[名稱] = 插件;

    // 為所有子類插件添加一個播放器原型方法(但不是
    // 基插件類)。
    如果(名稱!== BASE_PLUGIN_NAME){
      如果(插件.isBasic(插件)){
        Player.prototype[名稱] = createBasicPlugin(名稱, 插件);
      }其他{
        Player.prototype[名稱] = createPluginFactory(名稱, 插件);
      }
    }

    返回插件;
  }

  /**
   * 取消註冊 Video.js 插件。
   *
   * @param {string} 名稱
   * 要註銷的插件名稱。必須是一個字符串
   * 匹配現有插件。
   *
   * @throws {錯誤}
   * 如果嘗試取消註冊基本插件。
   */
  靜態取消註冊插件(名稱){
    如果(名稱=== BASE_PLUGIN_NAME){
      throw new Error('無法註銷基本插件。');
    }
    如果(插件存在(名稱)){
      刪除插件存儲[名稱];
      刪除 Player.prototype[名稱];
    }
  }

  /**
   * 獲取包含多個 Video.js 插件的對象。
   *
   * @param {數組} [名稱]
   * 如果提供,應該是一個插件名稱數組。默認為_all_
   * 插件名稱。
   *
   * @return {對象|未定義}
   * 包含與其名稱關聯的插件的對像或
   * `undefined` 如果不存在匹配的插件)。
   */
  靜態 getPlugins(名稱 = Object.keys(pluginStorage)){
    讓結果;

    names.forEach(名稱=> {
      const plugin = getPlugin(名稱);

      如果(插件){
        結果=結果|| {};
        結果[名稱] = 插件;
      }
    });

    返回結果;
  }

  /**
   * 獲取插件的版本(如果可用)
   *
   * @param {string} 名稱
   * 插件名稱。
   *
   * @return {字符串}
   * 插件的版本或空字符串。
   */
  靜態 getPluginVersion(名稱){
    const plugin = getPlugin(名稱);

    返回插件 && plugin.VERSION || '';
  }
}

/**
 * 如果存在,則按名稱獲取插件。
 *
 * @靜止的
 * @method 獲取插件
 * @memberOf 插件
 * @param {string} 名稱
 * 插件名稱。
 *
 * @returns {函數|未定義}
 * 插件(或 `undefined`)。
 */
Plugin.getPlugin = getPlugin;

/**
 * 註冊的基礎插件類的名稱。
 *
 * @type {字符串}
 */
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;

Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);

/**
 * 記錄在 player.js 中
 *
 * @忽略
 */
Player.prototype.usingPlugin = function(name) {
  返回!!這個 [插件快取密鑰] && 這個 [插件 _ 快取密鑰] [名稱] === 真;
};

/**
 * 記錄在 player.js 中
 *
 * @忽略
 */
Player.prototype.hasPlugin = 函數(名稱){
  返回!!插件存在(名稱);
};

導出默認插件;

/**
 * 表示即將在播放器上安裝插件的信號。
 *
 * @event Player#beforepluginsetup
 * @type {插件~PluginEventHash}
 */

/**
 * 插件即將在播放器上安裝的信號 - 按名稱。名字
 * 是插件的名稱。
 *
 * @event 播放器 #beforepluginsetup: $ 名稱
 * @type {插件~PluginEventHash}
 */

/**
 * 表示剛剛在播放器上安裝了插件。
 *
 * @event Player#pluginsetup
 * @type {插件~PluginEventHash}
 */

/**
 * 表示剛剛在播放器上設置了插件 - 按名稱。名字
 * 是插件的名稱。
 *
 * @event 播放器 #pluginsetup: $ 名稱
 * @type {插件~PluginEventHash}
 */

/**
 * @typedef {Object} 插件~PluginEventHash
 *
 * @property {string} 實例
 * 對於基本插件,插件函數的返回值。為了
 * 高級插件,觸發事件的插件實例。
 *
 * @property {string} 名稱
 * 插件名稱。
 *
 * @property {string} 插件
 * 對於基本插件,插件功能。對於高級插件,
 * 插件類/構造函數。
 */