HTML5 Video Player Enhance

To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness)[Key: E/W],飽和度(saturate)[Key: U/Y],對比度(contrast)[Key: T/R],速度(playback speed)[Key: Z/X/C]; frame [Key: F/D], hue [Key: O/I], blur [Key: K/J]; RESET [Key:Q] ; ROTATION [Key:S]

Mint 2019.09.15.. Lásd a legutóbbi verzió

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         HTML5 Video Player Enhance
// @version      2.5.4(tw)
// @description  To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness)[Key: E/W],飽和度(saturate)[Key: U/Y],對比度(contrast)[Key: T/R],速度(playback speed)[Key: Z/X/C]; frame [Key: F/D], hue [Key: O/I], blur [Key: K/J]; RESET [Key:Q] ; ROTATION [Key:S]  
// @author       CY Fung
// @match        http://*/*
// @match        https://*/*
// @run-at       document-start
// @grant        GM_addStyle
// @namespace https://greasyfork.org/users/371179
// ==/UserScript==

/* 元素FULLSCREEN-API,同時相容網頁FULLSCREEN */
class FullScreen {
  constructor (dom, pageMode) {
    this.dom = dom
    // 默認FULLSCREEN模式,如果傳入pageMode則表示進行的是頁面FULLSCREEN操作
    this.pageMode = pageMode || false
    let fullPageStyle = `
			._webfullscreen_ {
				display: block !important;
				position: fixed !important;
				width: 100% !important;
				height: 100% !important;
				top: 0 !important;
				left: 0 !important;
				background: #000 !important;
			}
			._webfullscreen_zindex_ {
				z-index: 999998 !important;
			}
		`
    if (!window._hasInitFullPageStyle_) {
      GM_addStyle(fullPageStyle)
      window._hasInitFullPageStyle_ = true
    }
  }

  eachParentNode (dom, fn) {
    let parent = dom.parentNode
    while (parent && parent.classList) {
      let isEnd = fn(parent, dom)
      parent = parent.parentNode
      if (isEnd) {
        break
      }
    }
  }

  getContainer () {
    let t = this
    if (t._container_) return t._container_

    let d = t.dom
    let domBox = d.getBoundingClientRect()
    let container = d
    t.eachParentNode(d, function (parentNode) {
      let parentBox = parentNode.getBoundingClientRect()
      if (parentBox.width <= domBox.width && parentBox.height <= domBox.height) {
        container = parentNode
      } else {
        return true
      }
    })
    t._container_ = container
    return container
  }

  isFull () {
    return this.dom.classList.contains('_webfullscreen_')
  }

  isFullScreen () {
    let d = document
    return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
      d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement)
  }

  enterFullScreen () {
    let c = this.getContainer()
    let enterFn = c.requestFullscreen || c.webkitRequestFullScreen || c.mozRequestFullScreen || c.msRequestFullScreen
    enterFn && enterFn.call(c)
  }

  enter () {
    let t = this
    if (t.isFull()) return
    let container = t.getContainer()
    let needSetIndex = false
    if (t.dom === container) {
      needSetIndex = true
    }
    this.eachParentNode(t.dom, function (parentNode) {
      parentNode.classList.add('_webfullscreen_')
      if (container === parentNode || needSetIndex) {
        needSetIndex = true
        parentNode.classList.add('_webfullscreen_zindex_')
      }
    })
    t.dom.classList.add('_webfullscreen_')
    let fullScreenMode = !t.pageMode
    if (fullScreenMode) {
      t.enterFullScreen()
    }
  }

  exitFullScreen () {
    let d = document
    let exitFn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen
    exitFn && exitFn.call(d)
  }

  exit () {
    let t = this
    t.dom.classList.remove('_webfullscreen_')
    this.eachParentNode(t.dom, function (parentNode) {
      parentNode.classList.remove('_webfullscreen_')
      parentNode.classList.remove('_webfullscreen_zindex_')
    })
    let fullScreenMode = !t.pageMode
    if (fullScreenMode || t.isFullScreen()) {
      t.exitFullScreen()
    }
  }

  toggle () {
    this.isFull() ? this.exit() : this.enter()
  }
}

(function () {
  /**
   * 任務配置中心 Task Control Center
   * 用於配置所有無法進行通用處理的任務,如不同網站的FULLSCREEN方式不一樣,必須調用網站本身的FULLSCREEN邏輯,才能確保字幕、彈幕等正常工作
   * */
  const TCC = {
    /**
     * 配置示例
     * 父級鍵名對應的是一級域名,
     * 子級鍵名對應的相關功能名稱,鍵值對應的該功能要觸發的點擊選擇器或者要調用的相關函數
     * 所有子級的鍵值都支持使用選擇器觸發或函數調用
     * 配置了子級的則使用子級配置邏輯進行操作,否則使用默認邏輯
     * 注意:include,exclude這兩個子級鍵名除外,這兩個是用來進行url範圍匹配的
     * */
    'demo.demo': {
      'fullScreen': '.fullscreen-btn',
      'exitFullScreen': '.exit-fullscreen-btn',
      'webFullScreen': function () {},
      'exitWebFullScreen': '.exit-fullscreen-btn',
      'autoPlay': '.player-start-btn',
      'pause': '.player-pause',
      'play': '.player-play',
      'switchPlayStatus': '.player-play',
      'playbackRate': function () {},
      'currentTime': function () {},
      'addCurrentTime': '.add-currenttime',
      'subtractCurrentTime': '.subtract-currenttime',
      // 自定義快捷鍵的執行方式,如果是組合鍵,必須是 ctrl-->shift-->alt 這樣的順序,沒有可以忽略,鍵名必須全小寫
      'shortcuts': {
        /* 註冊要執行自定義回調操作的快捷鍵 */
        register: [
          'ctrl+shift+alt+c',
          'ctrl+shift+c',
          'ctrl+alt+c',
          'ctrl+c',
          'c'
        ],
        /* 自定義快捷鍵的回調操作 */
        callback: function (h5Player, taskConf, data) {
          let { event, player } = data
          console.log(event, player)
        }
      },
      /* 當前域名下需包含的路徑信息,默認整個域名下所有路徑可用 必須是正則 */
      include: /^.*/,
      /* 當前域名下需排除的路徑信息,默認不排除任何路徑 必須是正則 */
      exclude: /\t/
    },
    'youtube.com': {
      // 'webFullScreen': 'button.ytp-size-button',
      'fullScreen': 'button.ytp-fullscreen-button'
    },
    'netflix.com': {
      'fullScreen': 'button.button-nfplayerFullscreen',
      'addCurrentTime': 'button.button-nfplayerFastForward',
      'subtractCurrentTime': 'button.button-nfplayerBackTen'
    },
    'bilibili.com': {
      'fullScreen': '[data-text="進入FULLSCREEN"]',
      'webFullScreen': '[data-text="網頁FULLSCREEN"]',
      'autoPlay': '.bilibili-player-video-btn-start',
      'switchPlayStatus': '.bilibili-player-video-btn-start'
    },
    'live.bilibili.com': {
      'fullScreen': '.bilibili-live-player-video-controller-fullscreen-btn button',
      'webFullScreen': '.bilibili-live-player-video-controller-web-fullscreen-btn button',
      'switchPlayStatus': '.bilibili-live-player-video-controller-start-btn button'
    },
    'iqiyi.com': {
      'fullScreen': '.iqp-btn-fullscreen',
      'webFullScreen': '.iqp-btn-webscreen',
      'init': function (h5Player, taskConf) {
        // 隱藏水印
        hideDom('.iqp-logo-box')
        // 移除暫停廣告
        GM_addStyle(`
          div[templatetype="common_pause"]{ display:none }
        `)
      }
    },
    'youku.com': {
      'fullScreen': '.control-fullscreen-icon',
      'init': function (h5Player, taskConf) {
        // 隱藏水印
        hideDom('.youku-layer-logo')
      }
    },
    'ted.com': {
      'fullScreen': 'button.Fullscreen'
    },
    'v.qq.com': {
      'pause': '.container_inner .txp-shadow-mod]',
      'play': '.container_inner .txp-shadow-mod',
      'shortcuts': {
        register: ['c', 'x', 'z'],
        callback: function (h5Player, taskConf, data) {
          let { event } = data
          let key = event.key.toLowerCase()
          let speedItems = document.querySelectorAll('.container_inner txpdiv[data-role="txp-button-speed-list"] .txp_menuitem')

          /* 利用sessionStorage下的playbackRate進行設置 */
          if (window.sessionStorage.playbackRate && /(c|x|z)/.test(key)) {
            let curSpeed = Number(window.sessionStorage.playbackRate)
            let perSpeed = curSpeed - 0.1 >= 0 ? curSpeed - 0.1 : 0.1
            let nextSpeed = curSpeed + 0.1 <= 4 ? curSpeed + 0.1 : 4
            let targetSpeed = curSpeed
            switch (key) {
              case 'z' :
                targetSpeed = 1
                break
              case 'c' :
                targetSpeed = nextSpeed
                break
              case 'x' :
                targetSpeed = perSpeed
                break
            }
            window.sessionStorage.playbackRate = targetSpeed
            h5Player.setCurrentTime(0.1, true)
            h5Player.setPlaybackRate(targetSpeed, true)
            return true
          }

          /* 模擬點擊觸發 */
          if (speedItems.length >= 3 && /(c|x|z)/.test(key)) {
            let curIndex = 1
            speedItems.forEach((item, index) => {
              if (item.classList.contains('txp_current')) {
                curIndex = index
              }
            })
            let perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0
            let nextIndex = curIndex + 1 < speedItems.length ? curIndex + 1 : speedItems.length - 1

            let target = speedItems[1]
            switch (key) {
              case 'z' :
                target = speedItems[1]
                break
              case 'c' :
                target = speedItems[nextIndex]
                break
              case 'x' :
                target = speedItems[perIndex]
                break
            }

            target.click()
            let speedNum = Number(target.innerHTML.replace('x'))
            h5Player.setPlaybackRate(speedNum)
          }
        }
      },
      'init': function (h5Player, taskConf) {
        // 隱藏水印
        hideDom('.txp-watermark')
      }
    },
    'pan.baidu.com': {
      'fullScreen': function (h5Player, taskConf) {
        h5Player.playerInstance.parentNode.querySelector('.vjs-fullscreen-control').click()
      }
    },

    /**
     * 獲取域名 , 目前實現方式不好,需改造,對地區性域名(如com.cn)、三級及以上域名支持不好
     * */
    getDomain: function () {
      let host = window.location.host
      let domain = host
      let tmpArr = host.split('.')
      if (tmpArr.length > 2) {
        tmpArr.shift()
        domain = tmpArr.join('.')
      }
      return domain
    },
    /**
     * 格式化配置任務
     * @param isAll { boolean } -可選 默認只格式當前域名或host下的配置任務,傳入true則將所有域名下的任務配置都進行格式化
     */
    formatTCC: function (isAll) {
      let t = this
      let keys = Object.keys(t)
      let domain = t.getDomain()
      let host = window.location.host

      function formatter (item) {
        let defObj = {
          include: /^.*/,
          exclude: /\t/
        }
        item.include = item.include || defObj.include
        item.exclude = item.exclude || defObj.exclude
        return item
      }

      let result = {}
      keys.forEach(function (key) {
        let item = t[key]
        if (isObj(item)) {
          if (isAll) {
            item = formatter(item)
            result[key] = item
          } else {
            if (key === host || key === domain) {
              item = formatter(item)
              result[key] = item
            }
          }
        }
      })
      return result
    },
    /* 判斷所提供的配置任務是否適用於當前URL */
    isMatch: function (taskConf) {
      let url = window.location.href
      let isMatch = false
      if (taskConf.include.test(url)) {
        isMatch = true
      }
      if (taskConf.exclude.test(url)) {
        isMatch = false
      }
      return isMatch
    },
    /**
     * 獲取任務配置,只能獲取到當前域名下的任務配置信息
     * @param taskName {string} -可選 指定具體任務,默認返回所有類型的任務配置
     */
    getTaskConfig: function () {
      let t = this
      if (!t._hasFormatTCC_) {
        t.formatTCC()
        t._hasFormatTCC_ = true
      }
      let domain = t.getDomain()
      let taskConf = t[window.location.host] || t[domain]

      if (taskConf && t.isMatch(taskConf)) {
        return taskConf
      }

      return {}
    },
    /**
     * 執行當前頁面下的相應任務
     * @param taskName {object|string} -必選,可直接傳入任務配置對象,也可用是任務名稱的字符串信息,自己去查找是否有任務需要執行
     * @param data {object} -可選,傳給回調函數的數據
     */
    doTask: function (taskName, data) {
      let t = this
      let isDo = false
      if (!taskName) return isDo
      let taskConf = isObj(taskName) ? taskName : t.getTaskConfig()

      if (!isObj(taskConf) || !taskConf[taskName]) return isDo

      let task = taskConf[taskName]

      let wrapDom = h5Player.getPlayerWrapDom()

      if (taskName === 'shortcuts') {
        if (isObj(task) && getType(task.callback) === 'function') {
          task.callback(h5Player, taskConf, data)
          isDo = true
        }
      } else if (getType(task) === 'function') {
        task(h5Player, taskConf, data)
        isDo = true
      } else {
        /* 觸發選擇器上的點擊事件 */
        if (wrapDom && wrapDom.querySelector(task)) {
          // 在video的父元素裡查找,是為了盡可能相容多實例下的邏輯
          wrapDom.querySelector(task).click()
          isDo = true
        } else if (document.querySelector(task)) {
          document.querySelector(task).click()
          isDo = true
        }
      }
      return isDo
    }
  }

  /**
   * 元素監聽器
   * @param selector -必選
   * @param fn -必選,元素存在時的回調
   * @param shadowRoot -可選 指定監聽某個shadowRoot下面的DOM元素
   * 參考:https://javascript.ruanyifeng.com/dom/mutationobserver.html
   */
  function ready (selector, fn, shadowRoot) {
    let listeners = []
    let win = window
    let doc = shadowRoot || win.document
    let MutationObserver = win.MutationObserver || win.WebKitMutationObserver
    let observer

    function $ready (selector, fn) {
      // 儲存選擇器和回調函數
      listeners.push({
        selector: selector,
        fn: fn
      })
      if (!observer) {
        // 監聽document變化
        observer = new MutationObserver(check)
        observer.observe(shadowRoot || doc.documentElement, {
          childList: true,
          subtree: true
        })
      }
      // 檢查該節點是否已經在DOM中
      check()
    }

    function check () {
      for (let i = 0; i < listeners.length; i++) {
        var listener = listeners[i]
        var elements = doc.querySelectorAll(listener.selector)
        for (let j = 0; j < elements.length; j++) {
          var element = elements[j]
          if (!element._isMutationReady_) {
            element._isMutationReady_ = true
            listener.fn.call(element, element)
          }
        }
      }
    }

    $ready(selector, fn)
  }

  function hideDom (selector, delay) {
    setTimeout(function () {
      const dom = document.querySelector(selector)
      if (dom) {
        dom.style.opacity = 0
      }
    }, delay || 1000 * 3)
  }

  /**
   * 某些網頁用了attachShadow closed mode,需要open才能獲取video標籤,例如百度雲盤
   * 解決參考:
   * https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed
   * https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension
   */
  function hackAttachShadow () {
    if (window._hasHackAttachShadow_) return
    try {
      window._shadowDomList_ = []
      window.Element.prototype._attachShadow = window.Element.prototype.attachShadow
      window.Element.prototype.attachShadow = function () {
        let arg = arguments
        if (arg[0] && arg[0]['mode']) {
          // 強制使用 open mode
          arg[0]['mode'] = 'open'
        }
        let shadowRoot = this._attachShadow.apply(this, arg)
        // 存一份shadowDomList
        window._shadowDomList_.push(shadowRoot)

        // 在document下面添加 addShadowRoot 自定義事件
        let shadowEvent = new window.CustomEvent('addShadowRoot', {
          shadowRoot,
          detail: {
            shadowRoot,
            message: 'addShadowRoot',
            time: new Date()
          },
          bubbles: true,
          cancelable: true
        })
        document.dispatchEvent(shadowEvent)

        return shadowRoot
      }
      window._hasHackAttachShadow_ = true
    } catch (e) {
      console.error('hackAttachShadow error by h5player plug-in')
    }
  }
  hackAttachShadow()

  /* 事件偵聽hack */
  function hackEventListener () {
    const EVENT = window.EventTarget.prototype
    if (EVENT._addEventListener) return
    EVENT._addEventListener = EVENT.addEventListener
    EVENT._removeEventListener = EVENT.removeEventListener
    window._listenerList_ = []

    // hack addEventListener
    EVENT.addEventListener = function () {
      let arg = arguments
      let type = arg[0]
      let listener = arg[1]
      this._addEventListener.apply(this, arg)
      this._listeners = this._listeners || {}
      this._listeners[type] = this._listeners[type] || []
      let listenerObj = {
        target: this,
        type,
        listener,
        options: arg[2],
        addTime: new Date().getTime()
      }
      window._listenerList_.push(listenerObj)
      this._listeners[type].push(listenerObj)
    }

    // hack removeEventListener
    EVENT.removeEventListener = function () {
      let arg = arguments
      let type = arg[0]
      let listener = arg[1]
      this._removeEventListener.apply(this, arg)
      this._listeners = this._listeners || {}
      this._listeners[type] = this._listeners[type] || []

      let result = []
      this._listeners[type].forEach(function (listenerObj) {
        if (listenerObj.listener !== listener) {
          result.push(listenerObj)
        }
      })
      this._listeners[type] = result
    }
  }
  hackEventListener()

  let quickSort = function (arr) {
    if (arr.length <= 1) { return arr }
    var pivotIndex = Math.floor(arr.length / 2)
    var pivot = arr.splice(pivotIndex, 1)[0]
    var left = []
    var right = []
    for (var i = 0; i < arr.length; i++) {
      if (arr[i] < pivot) {
        left.push(arr[i])
      } else {
        right.push(arr[i])
      }
    }
    return quickSort(left).concat([pivot], quickSort(right))
  }

  /**
   * 向上查找操作
   * @param dom {Element} -必選 初始dom元素
   * @param fn {function} -必選 每一級ParentNode的回調操作
   * 如果函數返回true則表示停止向上查找動作
   */
  function eachParentNode (dom, fn) {
    let parent = dom.parentNode
    while (parent) {
      let isEnd = fn(parent, dom)
      parent = parent.parentNode
      if (isEnd) {
        break
      }
    }
  }

  /**
   * 準確地獲取對象的具體類型
   * @param obj { all } -必選 要判斷的對象
   * @returns {*} 返回判斷的具體類型
   */
  function getType (obj) {
    if (obj == null) {
      return String(obj)
    }
    return typeof obj === 'object' || typeof obj === 'function'
      ? (obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase()) ||
      /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase()
      : typeof obj
  }

  function isObj (obj) {
    return getType(obj) === 'object'
  }

  /**
   * 深度合併兩個可枚舉的對象
   * @param objA {object} -必選 對象A
   * @param objB {object} -必選 對象B
   * @param concatArr {boolean} -可選 合併數組,默認遇到數組的時候,直接以另外一個數組替換當前數組,將此設置true則,遇到數組的時候一律合併,而不是直接替換
   * @returns {*|void}
   */
  function mergeObj (objA, objB, concatArr) {
    function isObj (obj) {
      return Object.prototype.toString.call(obj) === '[object Object]'
    }
    function isArr (arr) {
      return Object.prototype.toString.call(arr) === '[object Array]'
    }
    if (!isObj(objA) || !isObj(objB)) return objA
    function deepMerge (objA, objB) {
      let keys = Object.keys(objB)
      keys.forEach(function (key) {
        let subItemA = objA[key]
        let subItemB = objB[key]
        if (typeof subItemA === 'undefined') {
          objA[key] = subItemB
        } else {
          if (isObj(subItemA) && isObj(subItemB)) {
            /* 進行深層合併 */
            objA[key] = deepMerge(subItemA, subItemB)
          } else {
            if (concatArr && isArr(subItemA) && isArr(subItemB)) {
              objA[key] = subItemA.concat(subItemB)
            } else {
              objA[key] = subItemB
            }
          }
        }
      })
      return objA
    }
    return deepMerge(objA, objB)
  }

  /**
   * 多對象深度合併,合併規則基於mergeObj,但不存在concatArr選項
   * @returns {*}
   */
  function merge () {
    let result = arguments[0]
    for (var i = 0; i < arguments.length; i++) {
      if (i) {
        result = mergeObj(result, arguments[i])
      }
    }
    return result
  }

  /* ua信息偽裝 */
  function fakeUA (ua) {
    Object.defineProperty(navigator, 'userAgent', {
      value: ua,
      writable: false,
      configurable: false,
      enumerable: true
    })
  }

  /* ua信息來源:https://developers.whatismybrowser.com */
  const userAgentMap = {
    android: {
      chrome: 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36',
      firefox: 'Mozilla/5.0 (Android 7.0; Mobile; rv:57.0) Gecko/57.0 Firefox/57.0'
    },
    iPhone: {
      safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
      chrome: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.121 Mobile/15E148 Safari/605.1'
    },
    iPad: {
      safari: 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
      chrome: 'Mozilla/5.0 (iPad; CPU OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.155 Mobile/15E148 Safari/605.1'
    }
  }

  const fakeConfig = {
    // 'tv.cctv.com': userAgentMap.iPhone.chrome,
    // 'v.qq.com': userAgentMap.iPad.chrome,
    'open.163.com': userAgentMap.iPhone.chrome,
    'm.open.163.com': userAgentMap.iPhone.chrome
  }

  function debugMsg () {
    let arg = Array.from(arguments)
    arg.unshift('h5player debug message :')
    console.info.apply(console, arg)
  }

  let h5Player = {
    /* 提示文本的字號 */
    fontSize: 16,
    enable: true,
    globalMode: true,
    playerInstance: null,
    scale: 1,
    translate: {
      x: 0,
      y: 0
    },
    playbackRate: 1,
    /* 快進快退步長 */
    skipStep: 5,
    /* 獲取當前播放器的實例 */
    player: function () {
      let t = this
      return t.playerInstance || t.getPlayerList()[0]
    },
    /* 每個網頁可能存在的多個video播放器 */
    getPlayerList: function () {
      let list = []
      function findPlayer (context) {
        context.querySelectorAll('video').forEach(function (player) {
          list.push(player)
        })
      }
      findPlayer(document)

      // 被封裝在 shadow dom 裡面的video
      if (window._shadowDomList_) {
        window._shadowDomList_.forEach(function (shadowRoot) {
          findPlayer(shadowRoot)
        })
      }

      return list
    },
    getPlayerWrapDom: function () {
      let t = this
      let player = t.player()
      if (!player) return

      let wrapDom = null
      let playerBox = player.getBoundingClientRect()
      eachParentNode(player, function (parent) {
        if (parent === document || !parent.getBoundingClientRect) return
        let parentBox = parent.getBoundingClientRect()
        if (parentBox.width && parentBox.height) {
          if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) {
            wrapDom = parent
          }
        }
      })
      return wrapDom
    },
    /**
     * 初始化播放器實例
     * @param isSingle 是否為單實例video標籤
     */
    initPlayerInstance: function (isSingle) {
      let t = this
      if (!t.playerInstance) return

      let player = t.playerInstance


      if(!player.hasAttribute('tabindex'))      player.setAttribute('tabindex','-1');
        if(!player.hasAttribute('playsinline'))        player.setAttribute('playsinline','playsinline');
        if(!player.hasAttribute('x-webkit-airplay'))        player.setAttribute('x-webkit-airplay','deny');
        if(!player.hasAttribute('preload'))         player.setAttribute('preload','auto');
       // <video id="viu-player_html5_api" tabindex="-1" class="vjs-tech" autoplay="" playsinline="playsinline" x-webkit-airplay="deny" preload="auto"></video>


      t.filter.reset()
      t.initTips()
      t.initPlaybackRate()
      t.isFoucs()



      /* 增加通用FULLSCREEN,網頁FULLSCREEN-api */
      player._fullScreen_ = new FullScreen(player)
      player._fullPageScreen_ = new FullScreen(player, true)

      if (!player._hasCanplayEvent_) {
        player.addEventListener('canplay', function (event) {
          t.initAutoPlay(player)
        })
        player._hasCanplayEvent_ = true
      }

      /* 播放的時候進行相關同步操作 */
      if (!player._hasPlayingInitEvent_) {
        let setPlaybackRateOnPlayingCount = 0
        player.addEventListener('playing', function (event) {
          if (setPlaybackRateOnPlayingCount === 0) {
            /* 同步之前設定的播放速度 */
            t.setPlaybackRate()

            if (isSingle === true) {
              /* 恢復播放進度和進行進度記錄 */
              t.setPlayProgress(player)
              setTimeout(function () {
                t.playProgressRecorder(player)
              }, 1000 * 3)
            }
          } else {
            t.setPlaybackRate(null, true)
          }
          setPlaybackRateOnPlayingCount += 1
        })
        player._hasPlayingInitEvent_ = true
      }

      /* 進行自定義初始化操作 */
      let taskConf = TCC.getTaskConfig()
      if (taskConf.init) {
        TCC.doTask('init', player)
      }
    },
    initPlaybackRate: function () {
      let t = this
      t.playbackRate = t.getPlaybackRate()
    },
    getPlaybackRate: function () {
      let t = this
      let playbackRate = window.localStorage.getItem('_h5_player_playback_rate_') || t.playbackRate
      return Number(Number(playbackRate).toFixed(1))
    },
    /* 設置播放速度 */
    setPlaybackRate: function (num, notips) {
      let taskConf = TCC.getTaskConfig()
      if (taskConf.playbackRate) {
        TCC.doTask('playbackRate')
        return
      }

      let t = this
      let player = t.player()
      let curPlaybackRate
      if (num) {
        num = Number(num)
        if (Number.isNaN(num)) {
          console.error('h5player: 播放速度轉換出錯')
          return false
        }
        if (num <= 0) {
          num = 0.1
        }
        num = Number(num.toFixed(1))
        curPlaybackRate = num
      } else {
        curPlaybackRate = t.getPlaybackRate()
      }

      /* 記錄播放速度的信息 */
      window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate)

      t.playbackRate = curPlaybackRate
      player.playbackRate = curPlaybackRate

      /* 本身處於1被播放速度的時候不再提示 */
      if (!num && curPlaybackRate === 1) return
      !notips && t.tips('Playback speed: ' + player.playbackRate + 'x')
    },
    /**
     * 初始化自動播放邏輯
     * 必須是配置了自動播放按鈕選擇器得的才會進行自動播放
     */
    initAutoPlay: function (p) {
      let t = this
      let player = p || t.player()

      // 在輪詢重試的時候,如果實例變了,或處於隱藏頁面中則不進行自動播放操作
      if (!player || (p && p !== t.player()) || document.hidden) return

      let taskConf = TCC.getTaskConfig()
      if (player && taskConf.autoPlay && player.paused) {
        TCC.doTask('autoPlay')
        if (player.paused) {
          // 輪詢重試
          if (!player._initAutoPlayCount_) {
            player._initAutoPlayCount_ = 1
          }
          player._initAutoPlayCount_ += 1
          if (player._initAutoPlayCount_ >= 10) {
            return false
          }
          setTimeout(function () {
            t.initAutoPlay(player)
          }, 200)
        }
      }
    },
    setWebFullScreen: function () {
      let t = this
      let player = t.player()
      let isDo = TCC.doTask('webFullScreen')
      if (!isDo && player && player._fullPageScreen_) {
        player._fullPageScreen_.toggle()
      }
    },
    setCurrentTime: function (num, notips) {
      if (!num) return
      num = Number(num)
      let _num = Math.abs(Number(num.toFixed(1)))

      let t = this
      let player = t.player()
      let taskConf = TCC.getTaskConfig()
      if (taskConf.currentTime) {
        TCC.doTask('currentTime')
        return
      }

      if (num > 0) {
        if (taskConf.addCurrentTime) {
          TCC.doTask('addCurrentTime')
        } else {
          player.currentTime += _num
          !notips && t.tips(_num + ' Sec. Forward')
        }
      } else {
        if (taskConf.subtractCurrentTime) {
          TCC.doTask('subtractCurrentTime')
        } else {
          player.currentTime -= _num
          !notips && t.tips(_num + ' Sec. Backward')
        }
      }
    },
    setVolume: function (num) {
      if (!num) return
      num = Number(num)
      let _num = Math.abs(Number(num.toFixed(2)))

      let t = this
      let player = t.player()
      if (num > 0) {
        if (player.volume < 1) {
          player.volume += _num
        }
      } else {
        if (player.volume > 0) {
          player.volume -= _num
        }
      }
      t.tips('Volume: ' + parseInt(player.volume * 100) + '%')
    },
    setFakeUA (ua) {
      ua = ua || userAgentMap.iPhone.safari

      /* 記錄設定的ua信息 */
      window.localStorage.setItem('_h5_player_user_agent_', ua)
      fakeUA(ua)
    },

    /* ua偽裝切換開關 */
    switchFakeUA (ua) {
      let customUA = window.localStorage.getItem('_h5_player_user_agent_')
      if (customUA) {
        window.localStorage.removeItem('_h5_player_user_agent_')
      } else {
        this.setFakeUA(ua)
      }

      debugMsg('ua', navigator.userAgent)
    },

    switchPlayStatus: function () {
      let t = this
      let player = t.player()
      let taskConf = TCC.getTaskConfig()
      if (taskConf.switchPlayStatus) {
        TCC.doTask('switchPlayStatus')
        return
      }

      if (player.paused) {
        if (taskConf.play) {
          TCC.doTask('play')
        } else {
          player.play()
          t.tips('Playback resumed')
        }
      } else {
        if (taskConf.pause) {
          TCC.doTask('pause')
        } else {
          player.pause()
          t.tips('Playback paused')
        }
      }
    },
    tipsClassName: 'html_player_enhance_tips',
    tips: function (str, duration) {
      let t = h5Player
      let player = t.player()
      if (!player) {
        console.log('h5Player Tips:', str)
        return true
      }

      let parentNode = player.parentNode
/*
      // 修復部分提示按鈕位置異常問題
      let backupStyle = parentNode.getAttribute('style-backup') || ''
      let defStyle = parentNode.getAttribute('style') || ''
      if (backupStyle === null) {
        parentNode.setAttribute('style-backup', defStyle)
        backupStyle = defStyle
      }
      let newStyleArr = backupStyle.split(';')

      let oldPosition = parentNode.getAttribute('def-position') || window.getComputedStyle(parentNode).position
      if (parentNode.getAttribute('def-position') === null) {
        parentNode.setAttribute('def-position', oldPosition || '')
      }
      if (['static', 'inherit', 'initial', 'unset', ''].includes(oldPosition)) {
        newStyleArr.push('position: relative')
      }

      let playerBox = player.getBoundingClientRect()
      newStyleArr.push('min-width:' + playerBox.width + 'px')
      newStyleArr.push('min-height:' + playerBox.height + 'px')
      parentNode.setAttribute('style', newStyleArr.join(';'))
*/
      let tipsSelector = '.' + t.tipsClassName
      let tipsDom = parentNode.querySelector(tipsSelector)||(t.initTips(),parentNode.querySelector(tipsSelector))

      if (!tipsDom) {
        console.log('init h5player tips dom error...')
        return false
      }



      let style = tipsDom.style



      if(str===false){
          tipsDom.innerText = '';
        clearTimeout(this.on_off[0])
        clearTimeout(this.on_off[1])
        clearTimeout(this.on_off[2])
        style.display = 'none'
        style.opacity = 0
      }else{


          if(duration===undefined) duration=2000

      tipsDom.innerText = str

      for (var i = 0; i < 3; i++) {
        if (this.on_off[i]) clearTimeout(this.on_off[i])
      }

        clearTimeout(this.on_off[0])
        clearTimeout(this.on_off[1])
        clearTimeout(this.on_off[2])
        //style.display = 'none'
        //style.opacity = 0
        t.on_off[0] = setTimeout(function () {
          style.display = 'block'
          style.opacity = 0.9
        }, 15)
        t.on_off[1] = setTimeout(function () {
          style.display = 0.7
        }, 50)

          if(duration>0){
        t.on_off[2] = setTimeout(function () {
          // 隱藏提示框和還原樣式
          style.display = 'none'
          style.opacity = 0
          //parentNode.setAttribute('style', backupStyle)
        }, duration)
          }

      }

    },
    /* 設置提示DOM的樣式 */
    initTips: function () {
      let t = this
      let player = t.player()
      let parentNode = player.parentNode

      if (parentNode.querySelector('.' + t.tipsClassName)) return

      let tipsStyle = `
        position: absolute;
        z-index: 999;
        font-size: ${t.fontSize || 16}px;
        padding: 10px;
        background: rgba(0,0,0,0.4);
        color:white;
        top: 50%;
        left: 50%;max-width:500px;max-height:50px;
        transform: translate(-50%,-50%);
        transition: all 500ms ease;
        opacity: 0;
        border-radius:3px;
        display: none;
        -webkit-font-smoothing: subpixel-antialiased;
        -moz-font-smoothing: subpixel-antialiased;
        -ms-font-smoothing: subpixel-antialiased;
        font-smoothing: subpixel-antialiased;
        font-family: 'microsoft yahei', Verdana, Geneva, sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-drag: none;
-khtml-user-select: none;
-moz-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
pointer-events: none;
user-select: none;
      `
      let tips = document.createElement('div')
      tips.setAttribute('style', tipsStyle)
      tips.setAttribute('class', t.tipsClassName)
        tips.setAttribute('unselectable','on')
      parentNode.appendChild(tips)
    },
    on_off: new Array(3),
    rotate: 0,
    fps: 30,
    /* 濾鏡效果 */
    filter: {
      key: {},
      view_units: {'hue-rotate':'deg','blur':'px'},
      setup: function () {
        var view = ''
        for(var view_key in this.key){
          var view_unit = this.view_units[view_key]||''
          view += view_key+'('+(+this.key[view_key]||0).toFixed(3)+view_unit+') '
          this.key[view_key] = Number(+this.key[view_key]||0)
        }
        h5Player.player().style.WebkitFilter = view
      },
      reset: function () {
        this.key['brightness'] = 1
        this.key['contrast'] = 1
        this.key['saturate'] = 1
        this.key['hue-rotate'] = 0
        this.key['blur'] = 0
        this.setup()
      }
    },
    _isFoucs: false,

    /* 播放器的聚焦事件 */
    isFoucs: function () {
      let t = h5Player
      let player = t.player()
      if (!player) return
        /*
      if(!document.documentElement.hasAttribute('_h5_player_mousemove')){
        FullScreen._evt_move_target=[];
        document.documentElement.setAttribute('_h5_player_mousemove','')
        document.addEventListener('mousemove', function (e) {
          let w=h5Player.playerInstance;
          let pfocus=(FullScreen._evt_move_target||[]).indexOf(w)>=0
          FullScreen._evt_move_target = e.path||[e.target]
          let cfocus=(FullScreen._evt_move_target||[]).indexOf(w)>=0
          if(pfocus^cfocus) w.focus()
          console.log(pfocus,cfocus)
            window.pp1=h5Player
            window.pp2=player
        },true)
      }*/
        player.addEventListener('mouseenter', function (e) {
            h5Player._isFoucs = true
        })
        player.addEventListener('mouseleave', function (e) {
            h5Player._isFoucs = false
        })

        player.ownerDocument.addEventListener('focusout', function(e){

            if(!this.hasFocus()){
                setTimeout(function(){
                    h5Player.tips('focus is lost',-1);
                },1);
            }

        },true)

        player.ownerDocument.addEventListener('focusin', function(e){

            if(this.hasFocus()){
                setTimeout(function(){
                    h5Player.tips(false);
                },1);
            }

        },true)

    },

    keyCodeList: [13, 16, 17, 18, 27, 32, 37, 38, 39, 40, 49, 50, 51, 52, 67, 68, 69, 70, 73, 74, 75, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 97, 98, 99, 100, 220],
    keyList: ['enter', 'shift', 'control', 'alt', 'escape', ' ', 'arrowleft', 'arrowright', 'arrowright', 'arrowup', 'arrowdown', 
              '1', '2', '3', '4', 'c', 'd', 'e', 'f', 'i', 'j', 'k', 'o', 'p', 'q', 'r', 's', 't', 'u', 'w', 'x', 'y', 'z', '\\', '|'],
    keyMap: {
      'enter': 13,
      'shift': 16,
      'ctrl': 17,
      'alt': 18,
      'esc': 27,
      'space': 32,
      '←': 37,
      '↑': 38,
      '→': 39,
      '↓': 40,
      '1': 49,
      '2': 50,
      '3': 51,
      '4': 52,
      'c': 67,
      'd': 68,
      'e': 69,
      'f': 70,
      'i': 73,
      'j': 74,
      'k': 75,
      'o': 79,
      'p': 80,
      'q': 81,
      'r': 82,
      's': 83,
      't': 84,
      'u': 85,
      'w': 87,
      'x': 88,
      'y': 89,
      'z': 90,
      'pad1': 97,
      'pad2': 98,
      'pad3': 99,
      'pad4': 100,
      '\\': 220
    },
    /* 播放器事件響應器 */
    palyerTrigger: function (player, event) {
      if (!player || !event) return
      let t = h5Player
      let keyCode = event.keyCode

      if(event.code=="Space"&&keyCode>128) keyCode=32;

      let key = event.key.toLowerCase()

      if (event.shiftKey && !event.ctrlKey && !event.altKey) {
        // 網頁FULLSCREEN
        if (key === 'enter') {
          t.setWebFullScreen()
        }

        // 進入或退出畫中畫模式
        if (key === 'p') {
          if (window._isPictureInPicture_) {
            document.exitPictureInPicture().then(() => {
              window._isPictureInPicture_ = null
            }).catch(() => {
              window._isPictureInPicture_ = null
            })
          } else {
            player.requestPictureInPicture && player.requestPictureInPicture().then(() => {
              window._isPictureInPicture_ = true
            }).catch(() => {
              window._isPictureInPicture_ = null
            })
          }
        }

        // 視頻畫面縮放相關事件
        let allowKeys = ['x', 'c', 'z', 'arrowright', 'arrowleft', 'arrowup', 'arrowdown']
        if (!allowKeys.includes(key)) return

        t.scale = Number(t.scale)
        switch (key) {
          // shift+X:視頻縮小 -0.1
          case 'x' :
            t.scale -= 0.1
            break
          // shift+C:視頻放大 +0.1
          case 'c' :
            t.scale += 0.1
            break
          // shift+Z:視頻恢復正常大小
          case 'z' :
            t.scale = 1
            t.translate = { x: 0, y: 0 }
            break
          case 'arrowright' :
            t.translate.x += 10
            break
          case 'arrowleft' :
            t.translate.x -= 10
            break
          case 'arrowup' :
            t.translate.y -= 10
            break
          case 'arrowdown' :
            t.translate.y += 10
            break
        }

        let scale = t.scale = Number(t.scale).toFixed(1)
        player.style.transform = `scale(${scale}) translate(${t.translate.x}px, ${t.translate.y}px)`
        let tipsMsg = `視頻縮放率:${scale * 100}%`
        if (t.translate.x) {
          tipsMsg += `,水平位移:${t.translate.x}px`
        }
        if (t.translate.y) {
          tipsMsg += `,垂直位移:${t.translate.y}px`
        }
        t.tips(tipsMsg)

        // 阻止事件冒泡
        event.stopPropagation()
        event.preventDefault()
        return true
      }

      // 防止其它無關組合鍵衝突
      if (event.altKey || event.ctrlKey || event.shiftKey) return


      var kControl = null

        switch(keyCode){


      // 方向鍵右→:快進3秒
      case 39:
        t.setCurrentTime(t.skipStep)
        break;

      // 方向鍵左←:後退3秒
      case 37:
        t.setCurrentTime(-t.skipStep)
        break;

      // 方向鍵上↑:音量升高 1%
      case 38:
        t.setVolume(0.01)
        break;
      // 方向鍵下↓:音量降低 1%
      case 40:
        t.setVolume(-0.01)
        break;

      // 空格鍵:暫停/播放
      case h5Player.keyMap.space:
        t.switchPlayStatus()
        break;

      // 按鍵X:減速播放 -0.1
      case h5Player.keyMap.x:
        if (player.playbackRate > 0) {
          t.setPlaybackRate(player.playbackRate - 0.1)
        }
        break;
      // 按鍵C:加速播放 +0.1
      case h5Player.keyMap.c:
        if (player.playbackRate < 16) {
          t.setPlaybackRate(player.playbackRate + 0.1)
        }
        break;
      // 按鍵Z:正常速度播放
      case h5Player.keyMap.z:
        player.playbackRate = 1
        t.setPlaybackRate(player.playbackRate)
        break;



      // 按鍵F:下一幀
      case h5Player.keyMap.f:
        if (window.location.hostname === 'www.netflix.com') return /* netflix 的F鍵是FULLSCREEN的意思 */
        if (!player.paused) player.pause()
        player.currentTime += Number(1 / t.fps)
        t.tips('Jump to: Next frame')
        break;
      // 按鍵D:上一幀
      case h5Player.keyMap.d:
        if (!player.paused) player.pause()
        player.currentTime -= Number(1 / t.fps)
        t.tips('Jump to: Previous frame')
        break;
      // 按鍵E:亮度增加%
      case h5Player.keyMap.e:
        kControl='brightness'
        t.filter.key[kControl] += 0.1
        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
        t.filter.setup()
        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;
      // 按鍵W:亮度減少%
      case h5Player.keyMap.w:
        kControl='brightness'
        if (t.filter.key[kControl] > 0) {
          t.filter.key[kControl] -= 0.1
          t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
          t.filter.setup()
        }
        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;

      // 按鍵T:對比度增加%
      case h5Player.keyMap.t:
        kControl='contrast'
        t.filter.key[kControl] += 0.1
        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
        t.filter.setup()
        t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;
      // 按鍵R:對比度減少%
      case h5Player.keyMap.r:
        kControl='contrast'
        if (t.filter.key[kControl] > 0) {
          t.filter.key[kControl] -= 0.1
          t.filter.key[kControl] = t.filter.key[1].toFixed(2)
          t.filter.setup()
        }
        t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;

      // 按鍵U:飽和度增加%
      case h5Player.keyMap.u:
        kControl='saturate'
        t.filter.key[kControl] += 0.1
        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
        t.filter.setup()
        t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;
      // 按鍵Y:飽和度減少%
      case h5Player.keyMap.y:
        kControl='saturate'
        if (t.filter.key[kControl] > 0) {
          t.filter.key[kControl] -= 0.1
          t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
          t.filter.setup()
        }
        t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
        break;

      // 按鍵O:色相增加 1 度
      case h5Player.keyMap.o:
        kControl='hue-rotate'
        t.filter.key['hue-rotate'] += 1
        t.filter.setup()
        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
        break;
      // 按鍵I:色相減少 1 度
      case h5Player.keyMap.i:
        kControl='hue-rotate'
        t.filter.key['hue-rotate'] -= 1
        t.filter.setup()
        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
        break;

      // 按鍵K:模糊增加 0.1 px
      case h5Player.keyMap.k:
        kControl='blur'
        t.filter.key[kControl] += 0.1
        t.filter.key[kControl] = (+t.filter.key[kControl]||0).toFixed(1)
        t.filter.setup()
        t.tips('Blur: ' + t.filter.key[kControl] + ' px')
        break;
      // 按鍵J:模糊減少 0.1 px
      case h5Player.keyMap.j:
        kControl='blur'
        if (t.filter.key[kControl] > 0) {
          t.filter.key[kControl] -= 0.1
          t.filter.key[kControl] = (+t.filter.key[kControl]||0).toFixed(1)
          t.filter.setup()
        }
        t.tips('Blur: ' + t.filter.key[kControl] + ' px')
        break;

      // 按鍵Q:圖像復位
      case h5Player.keyMap.q:
        t.filter.reset()
        t.tips('Video Filter Reset')
        break;

      // 按鍵S:畫面旋轉 90 度
      case h5Player.keyMap.s:
        t.rotate += 90
        if (t.rotate % 360 === 0) t.rotate = 0;
        player.style.transform = 'rotate(' + t.rotate + 'deg)'
        t.tips('Rotation:' + t.rotate + ' deg')
        break;

      // 按鍵迴車,進入FULLSCREEN
      case h5Player.keyMap.enter:
            if (!TCC.doTask('fullScreen') && player._fullScreen_) player._fullScreen_.toggle();
            break;

      default:

        // 按1-4設置播放速度 49-52;97-100
        if ((keyCode >= 49 && keyCode <= 52) || (keyCode >= 97 && keyCode <= 100)) {
            player.playbackRate = Number(event.key)
            t.setPlaybackRate(player.playbackRate)

        }
      }

      // 阻止事件冒泡
      event.stopPropagation()
      event.preventDefault()
      return true
    },

    /* 運行自定義的快捷鍵操作,如果運行了會返回true */
    runCustomShortcuts: function (player, event) {
      if (!player || !event) return
      let key = event.key.toLowerCase()
      let taskConf = TCC.getTaskConfig()
      let confIsCorrect = isObj(taskConf.shortcuts) &&
        Array.isArray(taskConf.shortcuts.register) &&
        taskConf.shortcuts.callback instanceof Function

      /* 判斷當前觸發的快捷鍵是否已被註冊 */
      function isRegister () {
        let list = taskConf.shortcuts.register

        /* 當前觸發的組合鍵 */
        let combineKey = []
        if (event.ctrlKey) {
          combineKey.push('ctrl')
        }
        if (event.shiftKey) {
          combineKey.push('shift')
        }
        if (event.altKey) {
          combineKey.push('alt')
        }
        combineKey.push(key)

        /* 通過循環判斷當前觸發的組合鍵和已註冊的組合鍵是否完全一致 */
        let hasReg = false
        list.forEach((shortcut) => {
          let regKey = shortcut.split('+')
          if (combineKey.length === regKey.length) {
            let allMatch = true
            regKey.forEach((key) => {
              if (!combineKey.includes(key)) {
                allMatch = false
              }
            })
            if (allMatch) {
              hasReg = true
            }
          }
        })

        return hasReg
      }

      if (confIsCorrect && isRegister()) {
        // 執行自定義快捷鍵操作
        TCC.doTask('shortcuts', {
          event,
          player,
          h5Player
        })

        return true
      } else {
        return false
      }
    },

    /* 判斷焦點是否處於可編輯元素 */
    isEditableTarget: function (target) {
      let isEditable = target.getAttribute && target.getAttribute('contenteditable') === 'true'
      let isInputDom = /INPUT|TEXTAREA|SELECT/.test(target.nodeName)
      return isEditable || isInputDom
    },

    /* 按鍵響應方法 */
    keydownEvent: function (event) {
      let t = h5Player
      let keyCode = event.keyCode
      let key = event.key.toLowerCase()
      let player = t.player()

      if(event.code=="Space"&&keyCode>128) keyCode=32;

      /* 處於可編輯元素中不執行任何快捷鍵 */
      if (t.isEditableTarget(event.target)) return

      /* shift+f 切換UA偽裝 */
      if (event.shiftKey && keyCode === 70) {
        t.switchFakeUA()
      }

      /* 未用到的按鍵不進行任何事件監聽 */
      let isInUseCode = t.keyCodeList.includes(keyCode) || t.keyList.includes(key)
      if (!isInUseCode) return

      if (!player) {
        // console.log('無可用的播放,不執行相關操作')
        return
      }

      /* 切換插件的可用狀態 */
      if (event.ctrlKey && keyCode === 32) {
        t.enable = !t.enable
        if (t.enable) {
          t.tips('啟用h5Player插件')
        } else {
          t.tips('禁用h5Player插件')
        }
      }

      if (!t.enable) {
        console.log('h5Player 已禁用~')
        return false
      }

      // 按ctrl+\ 鍵進入聚焦或取消聚焦狀態,用於視頻標籤被遮擋的場景
      if (event.ctrlKey && keyCode === 220) {
        t.globalMode = !t.globalMode
        if (t.globalMode) {
          t.tips('全局模式')
        } else {
          t.tips('禁用全局模式')
        }
      }



      /* 非全局模式下,不聚焦則不執行快捷鍵的操作 */
     // t._isFoucs=(FullScreen._evt_move_target||[]).indexOf(player)>=0
      if (!t.globalMode && !t._isFoucs) return

      /* 判斷是否執行了自定義快捷鍵操作,如果是則不再響應後面默認定義操作 */
      if (t.runCustomShortcuts(player, event) === true) return

      /* 響應播放器相關操作 */
      t.palyerTrigger(player, event)
    },

    /**
     * 獲取播放進度
     * @param player -可選 對應的h5 播放器對象, 如果不傳,則獲取到的是整個播放進度表,傳則獲取當前播放器的播放進度
     */
    getPlayProgress: function (player) {
      let progressMap = window.localStorage.getItem('_h5_player_play_progress_')
      if (!progressMap) {
        progressMap = {}
      } else {
        progressMap = JSON.parse(progressMap)
      }
      if (!player) {
        return progressMap
      } else {
        let keyName = window.location.href || player.src
        if (progressMap[keyName]) {
          return progressMap[keyName].progress
        } else {
          return player.currentTime
        }
      }
    },
    /* 播放進度記錄器 */
    playProgressRecorder: function (player) {
      let t = h5Player
      clearTimeout(player._playProgressTimer_)
      function recorder (player) {
        player._playProgressTimer_ = setTimeout(function () {
          let progressMap = t.getPlayProgress()

          let keyName = window.location.href || player.src
          let list = Object.keys(progressMap)

          /* 只保存最近10個視頻的播放進度 */
          if (list.length > 10) {
            /* 根據更新的時間戳,取出最早添加播放進度的記錄項 */
            let timeList = []
            list.forEach(function (keyName) {
              progressMap[keyName] && progressMap[keyName].t && timeList.push(progressMap[keyName].t)
            })
            timeList = quickSort(timeList)
            let timestamp = timeList[0]

            /* 刪除最早添加的記錄項 */
            list.forEach(function (keyName) {
              if (progressMap[keyName].t === timestamp) {
                delete progressMap[keyName]
              }
            })
          }

          /* 記錄當前播放進度 */
          progressMap[keyName] = {
            progress: player.currentTime,
            t: new Date().getTime()
          }

          /* 存儲播放進度表 */
          window.localStorage.setItem('_h5_player_play_progress_', JSON.stringify(progressMap))

          /* 循環偵聽 */
          recorder(player)
        }, 1000 * 2)
      }
      recorder(player)
    },
    /* 設置播放進度 */
    setPlayProgress: function (player, time) {
      if (!player) return
      let t = h5Player
      let curTime = Number(t.getPlayProgress(player))
      if (!curTime || Number.isNaN(curTime)) return

      player.currentTime = curTime || player.currentTime
      if (curTime > 3) {
        t.tips('- Playback Progress is restored -')
      }
    },
    /**
     * 檢測h5播放器是否存在
     * @param callback
     */
    detecH5Player: function () {
      let t = this
      let playerList = t.getPlayerList()

      if (playerList.length) {
        console.log(' - HTML5 Video is detected -')

        /* 單video實例標籤的情況 */
        if (playerList.length === 1) {
          t.playerInstance = playerList[0]
          t.initPlayerInstance(true)
        } else {
          /* 多video實例標籤的情況 */
          playerList.forEach(function (player) {
            /* 鼠標移到其上面的時候重新指定實例 */
            if (player._hasMouseRedirectEvent_) return
            player.addEventListener('mouseenter', function (event) {
              t.playerInstance = event.target
              t.initPlayerInstance(false)
            })
            player._hasMouseRedirectEvent_ = true

            /* 播放器開始播放的時候重新指向實例 */
            if (player._hasPlayingRedirectEvent_) return
            player.addEventListener('playing', function (event) {
              t.playerInstance = event.target
              t.initPlayerInstance(false)

              /* 同步之前設定的播放速度 */
              t.setPlaybackRate()
            })
            player._hasPlayingRedirectEvent_ = true
          })
        }
      }
    },
    /* 綁定相關事件 */
    bindEvent: function () {
      var t = this
      if (t._hasBindEvent_) return

      document.removeEventListener('keydown', t.keydownEvent)
      document.addEventListener('keydown', t.keydownEvent, true)

      /* 相容iframe操作 */
      if (window.top !== window && window.top.document) {
        window.top.document.removeEventListener('keydown', t.keydownEvent)
        window.top.document.addEventListener('keydown', t.keydownEvent, true)
      }
      t._hasBindEvent_ = true
    },

    init: function (global) {
      var t = this
      if (global) {
        /* 綁定鍵盤事件 */
        t.bindEvent()

        /**
         * 判斷是否需要進行ua偽裝
         * 下面方案暫時不可用
         * 由於部分網站跳轉至移動端後域名不一致,形成跨域問題
         * 導致無法同步偽裝配置而不斷死循環跳轉
         * eg. open.163.com
         * */
        // let customUA = window.localStorage.getItem('_h5_player_user_agent_')
        // debugMsg(customUA, window.location.href, window.navigator.userAgent, document.referrer)
        // if (customUA) {
        //   t.setFakeUA(customUA)
        //   alert(customUA)
        // } else {
        //   alert('ua false')
        // }

        /* 對配置了ua偽裝的域名進行偽裝 */
        let host = window.location.host
        if (fakeConfig[host]) {
          t.setFakeUA(fakeConfig[host])
        }
      } else {
        /* 檢測是否存在H5播放器 */
        t.detecH5Player()
      }
    },
    load: false
  }

  try {
    /* 初始化全局所需的相關方法 */
    h5Player.init(true)

    /* 檢測到有視頻標籤就進行初始化 */
    ready('video', function () {
      h5Player.init()
    })

    /* 檢測shadow dom 下面的video */
    document.addEventListener('addShadowRoot', function (e) {
      let shadowRoot = e.detail.shadowRoot
      ready('video', function (element) {
        h5Player.init()
      }, shadowRoot)
    })

    window.top._h5PlayerForDebug_ = h5Player
  } catch (e) {
    console.error('h5player:', e)
  }

  // document.addEventListener('visibilitychange', function () {
  //   if (!document.hidden) {
  //     h5Player.initAutoPlay()
  //   }
  // })
})()