從 './component.js' 導入組件;
從 './utils/merge-options.js' 導入 mergeOptions;
從“全局/文檔”導入文檔;
import * as browser from './utils/browser.js';
從“全局/窗口”導入窗口;
從 './utils/fn.js' 導入 * 作為 Fn;

常量默認值 = {
  跟踪閾值:20,
  生活公差:+15
};

/*
  當我們處於實時邊緣時進行跟踪,以及用於實時播放的其他助手 */

/**
 * 用於檢查實時時間並確定播放器何時播放的類
 * 位於活邊或活邊後面。
 */
類 LiveTracker 擴展組件 {

  /**
   * 創建此類的一個實例。
   *
   * @param {Player} 播放器
   * 此類應附加到的 `Player`。
   *
   * @param {對象} [選項]
   * 播放器選項的鍵/值存儲。
   *
   * @param {number} [options.trackingThreshold=20]
   * 活動窗口的秒數(seekableEnd - seekableStart)
   * 在 liveui 顯示之前需要有媒體。
   *
   * @param {number} [options.liveTolerance=15]
   * 我們必須落後於直播的秒數
   * 之前我們將被視為非直播。請注意,這只會
   * 在現場比賽時使用。這允許大的可搜索端
   * 更改不會影響我們是否活著。
   */
  構造函數(播放器,選項){
    // LiveTracker 不需要元素
    const options_ = mergeOptions(defaults, options, {createEl: false});

    超級(玩家,選項_);

    this.handleVisibilityChange_ = (e) => this.handleVisibilityChange(e);
    this.trackLiveHandler_ = () => this.trackLive_();
    this.handlePlay_ = (e) => this.handlePlay(e);
    this.handleFirstTimeupdate_ = (e) => this.handleFirstTimeupdate(e);
    this.handleSeeked_ = (e) => this.handleSeeked(e);
    this.seekToLiveEdge_ = (e) => this.seekToLiveEdge(e);

    這個.reset_();

    this.on(this.player_, 'durationchange', (e) => this.handleDurationchange(e));
    // 我們應該嘗試將 canplay 上的跟踪切換為原生播放引擎,例如 Safari
    // 在那之前可能沒有諸如 seekableEnd 之類的正確值
    this.on(this.player_, 'canplay', () => this.toggleTracking());

    // 如果文檔被隱藏,我們不需要跟踪實時回放,
    // 同樣,跟踪文檔何時被隱藏可以
    // 導致 CPU 峰值並最終導致 IE11 上的頁面崩潰。
    if (browser.IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
      this.on(document, 'visibilitychange', this.handleVisibilityChange_);
    }
  }

  /**
   * 根據文檔可見性切換跟踪
   */
  handleVisibilityChange() {
    如果(this.player_.duration()!==無限){
      返回;
    }

    如果(文件。隱藏){
      this.stopTracking();
    }其他{
      這個.startTracking();
    }
  }

  /**
   * 尋找結束變化時的所有跟踪功能
   * 並追踪我們應該到達終點的距離
   */
  trackLive_() {
    const seekable = this.player_.seekable();

    // 跳過未定義的可尋址
    如果(!seekable ||!seekable.length){
      返回;
    }

    const newTime = Number(window.performance.now().toFixed(4));
    const deltaTime = this.lastTime_ === -1 ?0 : (newTime - this.lastTime_) / 1000;

    this.lastTime_ = newTime;

    this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;

    const liveCurrentTime = this.liveCurrentTime();
    const currentTime = this.player_.currentTime();

    // 如果有任何真值,我們就落後於 live
    // 1. 播放器暫停
    // 2. 用戶尋找到距離現場 2 秒的位置
    // 3. live 和 current 時間差比較大
    // liveTolerance 默認為 15s
    讓 isBehind = this.player_.paused() || this.seekedBehindLive_ ||
      Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;

    // 我們不能落後 if
    // 1. 直到我們還沒有看到時間更新
    // 2. liveCurrentTime 為 Infinity,這發生在 Android 和 Native Safari 上
    如果(!this.timeupdateSeen_ || liveCurrentTime === Infinity){
      落後=假;
    }

    如果(isBehind !== this.behindLiveEdge_){
      this.behindLiveEdge_ = isBehind;
      this.trigger('liveedgechange');
    }
  }

  /**
   * 處理播放器上的 durationchange 事件
   * 並相應地開始/停止跟踪。
   */
  handleDurationchange() {
    this.toggleTracking();
  }

  /**
   * 開始/停止跟踪
   */
  切換跟踪(){
    if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
      如果(這個.player_.options_.liveui){
        this.player_.addClass('vjs-liveui');
      }
      這個.startTracking();
    }其他{
      this.player_.removeClass('vjs-liveui');
      this.stopTracking();
    }
  }

  /**
   * 開始追踪直播回放
   */
  開始跟踪(){
    如果(this.isTracking()){
      返回;
    }

    // 如果我們還沒有看到時間更新,我們需要檢查是否播放
    // 在此組件開始跟踪之前開始。這很常見
    // 當使用自動播放時。
    如果(!this.timeupdateSeen_){
      this.timeupdateSeen_ = this.player_.hasStarted();
    }

    this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, Fn.UPDATE_REFRESH_INTERVAL);
    這個.trackLive_();

    this.on(this.player_, ['播放', '暫停'], this.trackLiveHandler_);

    如果(!this.timeupdateSeen_){
      this.one(this.player_, 'play', this.handlePlay_);
      this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
    }其他{
      this.on(this.player_, 'seeked', this.handleSeeked_);
    }
  }

  /**
   * 如果播放器尚未播放,則處理播放器的第一次更新
   * 實時跟踪器開始跟踪時。
   */
  handleFirstTimeupdate() {
    this.timeupdateSeen_ = true;
    this.on(this.player_, 'seeked', this.handleSeeked_);
  }

  /**
   *跟踪搜索開始的時間,並聽取搜索
   * 找到搜索結束的地方。
   */
  handleSeeked() {
    const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());

    this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
    this.nextSeekedFromUser_ = false;
    這個.trackLive_();
  }

  /**
   * 處理播放器的第一次播放,並確保我們尋找
   * 就在現場邊緣。
   */
  handlePlay() {
    this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
  }

  /**
   * 停止跟踪,並將所有內部變量設置為
   * 它們的初始值。
   */
  重置_() {
    this.lastTime_ = -1;
    this.pastSeekEnd_ = 0;
    this.lastSeekEnd_ = -1;
    this.behindLiveEdge_ = true;
    this.timeupdateSeen_ = false;
    this.seekedBehindLive_ = false;
    this.nextSeekedFromUser_ = false;

    this.clearInterval(this.trackingInterval_);
    this.trackingInterval_ = null;

    this.off(this.player_, ['播放', '暫停'], this.trackLiveHandler_);
    this.off(this.player_, 'seeked', this.handleSeeked_);
    this.off(this.player_, 'play', this.handlePlay_);
    this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
    this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
  }

  /**
   * 下一個搜索事件來自用戶。這意味著任何尋求
   * > 直播落後 2 秒將被視為真正落後直播
   * liveTolerance 將被忽略。
   */
  nextSeekedFromUser() {
    this.nextSeekedFromUser_ = true;
  }

  /**
   * 停止跟踪直播回放
   */
  停止跟踪(){
    如果(!this.isTracking()){
      返回;
    }
    這個.reset_();
    this.trigger('liveedgechange');
  }

  /**
   * 幫助玩家獲得可搜索的結局
   * 這樣我們就不必到處進行空檢查
   *
   * @return {數字}
   * 最遠可尋的終點或無窮大。
   */
  seekableEnd() {
    const seekable = this.player_.seekable();
    const seekableEnds = [];
    讓我=可尋找的?可搜索的長度:0;

    當我 - ) {
      seekableEnds.push(seekable.end(i));
    }

    // 獲取排序後最遠的可搜索端,或者如果沒有
    // 默認為無窮大
    返回 seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] :無窮;
  }

  /**
   * 幫助玩家尋找起點
   * 這樣我們就不必到處進行空檢查
   *
   * @return {數字}
   * 最早的可搜索開始或 0。
   */
  seekableStart() {
    const seekable = this.player_.seekable();
    const seekableStarts = [];
    讓我=可尋找的?可搜索的長度:0;

    當我 - ) {
      seekableStarts.push(seekable.start(i));
    }

    // 獲取排序後的第一個可搜索的開始,或者如果沒有
    // 默認為 0
    返回 seekableStarts.length ? seekableStarts.sort()[0] :0;
  }

  /**
   * 獲取實時窗口又名
   * seekable 開始和之間的時間量
   * 直播當前時間。
   *
   * @return {數字}
   * 可搜索的秒數
   *現場視頻。
   */
  活窗(){
    const liveCurrentTime = this.liveCurrentTime();

    // 如果 liveCurrenTime 是 Infinity 那麼我們根本就沒有 liveWindow
    如果(liveCurrentTime === Infinity){
      返回 0;
    }

    返回 liveCurrentTime - this.seekableStart();
  }

  /**
   * 判斷播放器是否在線,只檢查這個組件是否
   * 是否跟踪實時回放
   *
   * @return {布爾值}
   * liveTracker是否正在跟踪
   */
  isLive() {
    返回 this.isTracking();
  }

  /**
   * 判斷currentTime是否處於實時邊緣,不會落後
   * 在每個 seekableendchange 上
   *
   * @return {布爾值}
   * 播放是否處於現場邊緣
   */
  在LiveEdge(){
    返回 !this.behindLiveEdge();
  }

  /**
   * 得到我們期望的實時時間
   *
   * @return {數字}
   * 預計直播當前時間
   */
  liveCurrentTime() {
    返回 this.pastSeekEnd() + this.seekableEnd();
  }

  /**
   * seekable 結束後經過的秒數
   *改變了。一旦可搜索端發生變化,這將重置為 0。
   *
   * @return {數字}
   * 超過當前可搜索結束的秒數
   */
  過去搜索結束(){
    const seekableEnd = this.seekableEnd();

    if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
      this.pastSeekEnd_ = 0;
    }
    this.lastSeekEnd_ = seekableEnd;
    返回 this.pastSeekEnd_;
  }

  /**
   * 如果我們目前落後於實時邊緣,又名 currentTime 將是
   * 落後於 seekableendchange
   *
   * @return {布爾值}
   * 如果我們落後於實時邊緣
   */
  behindLiveEdge() {
    返回 this.behindLiveEdge_;
  }

  /**
   * 實時跟踪器當前是否正在跟踪。
   */
  是跟踪(){
    返回類型 this.trackingInterval_ === 'number';
  }

  /**
   * 如果我們落後於實時邊緣,則尋求實時邊緣
   */
  seekToLiveEdge() {
    this.seekedBehindLive_ = false;
    如果(this.atLiveEdge()){
      返回;
    }
    this.nextSeekedFromUser_ = false;
    this.player_.currentTime(this.liveCurrentTime());

  }

  /**
   * 處理 liveTracker
   */
  處置(){
    this.off(文檔, 'visibilitychange', this.handleVisibilityChange_);
    this.stopTracking();
    super.dispose();
  }
}

Component.registerComponent('LiveTracker', LiveTracker);
導出默認的 LiveTracker;