Greasy Fork is available in English.

HTML5播放器增强插件 - 修订版

对HTML5播放器的功能进行增强,支持所有使用H5进行视频播放的网站,快捷键仿照Potplayer的快捷键布局,实现调节亮度,饱和度,对比度,速度等功能。

2019/09/19時点のページです。最新版はこちら。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         HTML5播放器增强插件 - 修订版
// @namespace    https://github.com/xxxily/h5player
// @homepage     https://github.com/xxxily/h5player
// @version      2.6.1
// @description  对HTML5播放器的功能进行增强,支持所有使用H5进行视频播放的网站,快捷键仿照Potplayer的快捷键布局,实现调节亮度,饱和度,对比度,速度等功能。
// @author       ankvps
// @match        http://*/*
// @match        https://*/*
// @run-at       document-start
// @grant        GM_addStyle
// ==/UserScript==
(function (w) { if (w) { w.name = 'h5player'; } })();

/*!
 * @name         utils.js
 * @description  数据类型相关的方法
 * @version      0.0.1
 * @author       Blaze
 * @date         22/03/2019 22:46
 * @github       https://github.com/xxxily
 */

/**
 * 准确地获取对象的具体类型 参见:https://www.talkingcoder.com/article/6333557442705696719
 * @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
}

const isType = (obj, typeName) => getType(obj) === typeName;
const isObj = obj => isType(obj, 'object');

/**
 * 任务配置中心 Task Control Center
 * 用于配置所有无法进行通用处理的任务,如不同网站的全屏方式不一样,必须调用网站本身的全屏逻辑,才能确保字幕、弹幕等正常工作
 * */

class TCC {
  constructor (taskConf, doTaskFunc) {
    this.conf = taskConf || {
      /**
       * 配置示例
       * 父级键名对应的是一级域名,
       * 子级键名对应的相关功能名称,键值对应的该功能要触发的点击选择器或者要调用的相关函数
       * 所有子级的键值都支持使用选择器触发或函数调用
       * 配置了子级的则使用子级配置逻辑进行操作,否则使用默认逻辑
       * 注意: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) {
            const { event, player } = data;
            console.log(event, player);
          }
        },
        /* 当前域名下需包含的路径信息,默认整个域名下所有路径可用 必须是正则 */
        include: /^.*/,
        /* 当前域名下需排除的路径信息,默认不排除任何路径 必须是正则 */
        exclude: /\t/
      }
    };

    // 通过doTaskFunc回调定义配置该如何执行任务
    this.doTaskFunc = doTaskFunc instanceof Function ? doTaskFunc : function () {};
  }

  /**
   * 获取域名 , 目前实现方式不好,需改造,对地区性域名(如com.cn)、三级及以上域名支持不好
   * */
  getDomain () {
    const host = window.location.host;
    let domain = host;
    const tmpArr = host.split('.');
    if (tmpArr.length > 2) {
      tmpArr.shift();
      domain = tmpArr.join('.');
    }
    return domain
  }

  /**
   * 格式化配置任务
   * @param isAll { boolean } -可选 默认只格式当前域名或host下的配置任务,传入true则将所有域名下的任务配置都进行格式化
   */
  formatTCC (isAll) {
    const t = this;
    const keys = Object.keys(t.conf);
    const domain = t.getDomain();
    const host = window.location.host;

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

    const 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 (taskConf) {
    const 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 () {
    const t = this;
    if (!t._hasFormatTCC_) {
      t.formatTCC();
      t._hasFormatTCC_ = true;
    }
    const domain = t.getDomain();
    const taskConf = t[window.location.host] || t[domain];

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

    return {}
  }

  /**
   * 执行当前页面下的相应任务
   * @param taskName {object|string} -必选,可直接传入任务配置对象,也可用是任务名称的字符串信息,自己去查找是否有任务需要执行
   * @param data {object} -可选,传给回调函数的数据
   */
  doTask (taskName, data) {
    const t = this;
    let isDo = false;
    if (!taskName) return isDo
    const taskConf = isObj(taskName) ? taskName : t.getTaskConfig();

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

    const task = taskConf[taskName];

    if (task) {
      isDo = true;
      t.doTaskFunc(taskName, taskConf, data);
    }

    return isDo
  }
}

/**
 * 元素监听器
 * @param selector -必选
 * @param fn -必选,元素存在时的回调
 * @param shadowRoot -可选 指定监听某个shadowRoot下面的DOM元素
 * 参考:https://javascript.ruanyifeng.com/dom/mutationobserver.html
 */
function ready (selector, fn, shadowRoot) {
  const listeners = [];
  const win = window;
  const doc = shadowRoot || win.document;
  const 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);
}

/**
 * 某些网页用了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 () {
      const arg = arguments;
      if (arg[0] && arg[0].mode) {
        // 强制使用 open mode
        arg[0].mode = 'open';
      }
      const shadowRoot = this._attachShadow.apply(this, arg);
      // 存一份shadowDomList
      window._shadowDomList_.push(shadowRoot);

      // 在document下面添加 addShadowRoot 自定义事件
      const 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');
  }
}

/* 事件侦听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 () {
    const arg = arguments;
    const type = arg[0];
    const listener = arg[1];
    this._addEventListener.apply(this, arg);
    this._listeners = this._listeners || {};
    this._listeners[type] = this._listeners[type] || [];
    const 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 () {
    const arg = arguments;
    const type = arg[0];
    const listener = arg[1];
    this._removeEventListener.apply(this, arg);
    this._listeners = this._listeners || {};
    this._listeners[type] = this._listeners[type] || [];

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

const 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))
};

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

/**
 * 向上查找操作
 * @param dom {Element} -必选 初始dom元素
 * @param fn {function} -必选 每一级ParentNode的回调操作
 * 如果函数返回true则表示停止向上查找动作
 */
function eachParentNode (dom, fn) {
  let parent = dom.parentNode;
  while (parent) {
    const isEnd = fn(parent, dom);
    parent = parent.parentNode;
    if (isEnd) {
      break
    }
  }
}

/* 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'
  }
};

/**
 * 判断是否处于Iframe中
 * @returns {boolean}
 */
function isInIframe () {
  return window !== window.top
}

/**
 * 判断是否处于跨域限制的Iframe中
 * @returns {boolean}
 */
function isInCrossOriginFrame () {
  let result = true;
  try {
    if (window.top.localStorage || window.top.location.href) {
      result = false;
    }
  } catch (e) {
    result = true;
  }
  return result
}

/**
 * 任务配置中心 Task Control Center
 * 用于配置所有无法进行通用处理的任务,如不同网站的全屏方式不一样,必须调用网站本身的全屏逻辑,才能确保字幕、弹幕等正常工作
 * */

const taskConf = {
  /**
   * 配置示例
   * 父级键名对应的是一级域名,
   * 子级键名对应的相关功能名称,键值对应的该功能要触发的点击选择器或者要调用的相关函数
   * 所有子级的键值都支持使用选择器触发或函数调用
   * 配置了子级的则使用子级配置逻辑进行操作,否则使用默认逻辑
   * 注意: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) {
        const { 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="进入全屏"]',
    webFullScreen: '[data-text="网页全屏"]',
    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');
      // 移除暂停广告
      // eslint-disable-next-line no-undef
      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) {
        const { event } = data;
        const key = event.key.toLowerCase();
        const 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)) {
          const curSpeed = Number(window.sessionStorage.playbackRate);
          const perSpeed = curSpeed - 0.1 >= 0 ? curSpeed - 0.1 : 0.1;
          const 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;
            }
          });
          const perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0;
          const 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();
          const 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();
    }
  }
};

function h5PlayerTccInit (h5Player) {
  return new TCC(taskConf, function (taskName, taskConf, data) {
    const task = taskConf[taskName];
    const wrapDom = h5Player.getPlayerWrapDom();

    if (taskName === 'shortcuts') {
      if (isObj(task) && task.callback instanceof Function) {
        task.callback(h5Player, taskConf, data);
      }
    } else if (task instanceof Function) {
      task(h5Player, taskConf, data);
    } else {
      /* 触发选择器上的点击事件 */
      if (wrapDom && wrapDom.querySelector(task)) {
        // 在video的父元素里查找,是为了尽可能兼容多实例下的逻辑
        wrapDom.querySelector(task).click();
      } else if (document.querySelector(task)) {
        document.querySelector(task).click();
      }
    }
  })
}

/* ua伪装配置 */
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
};

/**
 * 元素全屏API,同时兼容网页全屏
 */

class FullScreen {
  constructor (dom, pageMode) {
    this.dom = dom;
    // 默认全屏模式,如果传入pageMode则表示进行的是页面全屏操作
    this.pageMode = pageMode || false;
    const 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_) {
      // eslint-disable-next-line no-undef
      GM_addStyle(fullPageStyle);
      window._hasInitFullPageStyle_ = true;
    }

    window.addEventListener('keyup', (event) => {
      const key = event.key.toLowerCase();
      if (key === 'escape') {
        this.exit();
      }
    }, true);
  }

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

  getContainer () {
    const t = this;
    if (t._container_) return t._container_

    const d = t.dom;
    const domBox = d.getBoundingClientRect();
    let container = d;
    t.eachParentNode(d, function (parentNode) {
      const 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 () {
    const d = document;
    return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
      d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement)
  }

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

  enter () {
    const t = this;
    if (t.isFull()) return
    const 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_');
    const fullScreenMode = !t.pageMode;
    if (fullScreenMode) {
      t.enterFullScreen();
    }
  }

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

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

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

(function () {
  hackAttachShadow();
  hackEventListener();

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

  let TCC = null;
  const h5Player = {
    /* 提示文本的字号 */
    fontSize: 16,
    enable: true,
    globalMode: true,
    playerInstance: null,
    scale: 1,
    translate: {
      x: 0,
      y: 0
    },
    playbackRate: 1,
    lastPlaybackRate: 1,
    /* 快进快退步长 */
    skipStep: 5,
    /* 获取当前播放器的实例 */
    player: function () {
      const t = this;
      return t.playerInstance || t.getPlayerList()[0]
    },
    /* 每个网页可能存在的多个video播放器 */
    getPlayerList: function () {
      const 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 () {
      const t = this;
      const player = t.player();
      if (!player) return

      let wrapDom = null;
      const playerBox = player.getBoundingClientRect();
      eachParentNode(player, function (parent) {
        if (parent === document || !parent.getBoundingClientRect) return
        const 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) {
      const t = this;
      if (!t.playerInstance) return

      const player = t.playerInstance;
      t.filter.reset();
      t.initTips();
      t.initPlaybackRate();
      t.isFoucs();

      /* 增加通用全屏,网页全屏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;
      }

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

      const t = this;
      const 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();
      }

      /* 记录播放速度的信息 */
      !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate);

      t.playbackRate = curPlaybackRate;
      player.playbackRate = curPlaybackRate;

      /* 本身处于1被播放速度的时候不再提示 */
      if (!num && curPlaybackRate === 1) return
      !notips && t.tips('播放速度:' + player.playbackRate + '倍');
    },
    /**
     * 初始化自动播放逻辑
     * 必须是配置了自动播放按钮选择器得的才会进行自动播放
     */
    initAutoPlay: function (p) {
      const t = this;
      const player = p || t.player();

      // 在轮询重试的时候,如果实例变了,或处于隐藏页面中则不进行自动播放操作
      if (!player || (p && p !== t.player()) || document.hidden) return

      const 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 () {
      const t = this;
      const player = t.player();
      const isDo = TCC.doTask('webFullScreen');
      if (!isDo && player && player._fullPageScreen_) {
        player._fullPageScreen_.toggle();
      }
    },
    setCurrentTime: function (num, notips) {
      if (!num) return
      num = Number(num);
      const _num = Math.abs(Number(num.toFixed(1)));

      const t = this;
      const player = t.player();
      const 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 + '秒');
        }
      } else {
        if (taskConf.subtractCurrentTime) {
          TCC.doTask('subtractCurrentTime');
        } else {
          player.currentTime -= _num;
          !notips && t.tips('后退:' + _num + '秒');
        }
      }
    },
    setVolume: function (num) {
      if (!num) return
      num = Number(num);
      const _num = Math.abs(Number(num.toFixed(2)));

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

      /* 记录设定的ua信息 */
      !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_user_agent_', ua);
      fakeUA(ua);
    },

    /* ua伪装切换开关 */
    switchFakeUA (ua) {
      const customUA = isInCrossOriginFrame() ? null : window.localStorage.getItem('_h5_player_user_agent_');
      if (customUA) {
        !isInCrossOriginFrame() && window.localStorage.removeItem('_h5_player_user_agent_');
      } else {
        this.setFakeUA(ua);
      }

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

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

      if (player.paused) {
        if (taskConf.play) {
          TCC.doTask('play');
        } else {
          player.play();
          t.tips('播放');
        }
      } else {
        if (taskConf.pause) {
          TCC.doTask('pause');
        } else {
          player.pause();
          t.tips('暂停');
        }
      }
    },
    tipsClassName: 'html_player_enhance_tips',
    tips: function (str) {
      const t = h5Player;
      const player = t.player();
      if (!player) {
        console.log('h5Player Tips:', str);
        return true
      }

      const parentNode = player.parentNode;

      // 修复部分提示按钮位置异常问题
      let backupStyle = parentNode.getAttribute('style-backup') || '';
      const defStyle = parentNode.getAttribute('style') || '';
      if (backupStyle === null) {
        parentNode.setAttribute('style-backup', defStyle);
        backupStyle = defStyle;
      }
      const newStyleArr = backupStyle.split(';');

      const 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');
      }

      const playerBox = player.getBoundingClientRect();
      newStyleArr.push('min-width:' + playerBox.width + 'px');
      newStyleArr.push('min-height:' + playerBox.height + 'px');
      parentNode.setAttribute('style', newStyleArr.join(';'));

      const tipsSelector = '.' + t.tipsClassName;
      let tipsDom = parentNode.querySelector(tipsSelector);

      /* 提示dom未初始化的,则进行初始化 */
      if (!tipsDom) {
        t.initTips();
        tipsDom = parentNode.querySelector(tipsSelector);
        if (!tipsDom) {
          console.log('init h5player tips dom error...');
          return false
        }
      }

      const style = tipsDom.style;
      tipsDom.innerText = str;

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

      function showTips () {
        style.display = 'block';
        t.on_off[0] = setTimeout(function () {
          style.opacity = 1;
        }, 50);
        t.on_off[1] = setTimeout(function () {
          // 隐藏提示框和还原样式
          style.opacity = 0;
          style.display = 'none';
          parentNode.setAttribute('style', backupStyle);
        }, 2000);
      }

      if (style.display === 'block') {
        style.display = 'none';
        clearTimeout(this.on_off[3]);
        t.on_off[2] = setTimeout(function () {
          showTips();
        }, 100);
      } else {
        showTips();
      }
    },
    /* 设置提示DOM的样式 */
    initTips: function () {
      const t = this;
      const player = t.player();
      const parentNode = player.parentNode;
      if (parentNode.querySelector('.' + t.tipsClassName)) return

      const tipsStyle = `
        position: absolute;
        z-index: 999999;
        font-size: ${t.fontSize || 16}px;
        padding: 10px;
        background: rgba(0,0,0,0.4);
        color:white;
        top: 50%;
        left: 50%;
        transform: translate(-50%,-50%);
        transition: all 500ms ease;
        opacity: 0;
        border-radius:3px;
        display: none;
        -webkit-font-smoothing: subpixel-antialiased;
        font-family: 'microsoft yahei', Verdana, Geneva, sans-serif;
        -webkit-user-select: none;
      `;
      const tips = document.createElement('div');
      tips.setAttribute('style', tipsStyle);
      tips.setAttribute('class', t.tipsClassName);
      parentNode.appendChild(tips);
    },
    on_off: new Array(3),
    rotate: 0,
    fps: 30,
    /* 滤镜效果 */
    filter: {
      key: new Array(5),
      setup: function () {
        var view = 'brightness({0}) contrast({1}) saturate({2}) hue-rotate({3}deg) blur({4}px)';
        for (var i = 0; i < 5; i++) {
          view = view.replace('{' + i + '}', String(this.key[i]));
          this.key[i] = Number(this.key[i]);
        }
        h5Player.player().style.WebkitFilter = view;
      },
      reset: function () {
        this.key[0] = 1;
        this.key[1] = 1;
        this.key[2] = 1;
        this.key[3] = 0;
        this.key[4] = 0;
        this.setup();
      }
    },
    _isFoucs: false,

    /* 播放器的聚焦事件 */
    isFoucs: function () {
      const t = h5Player;
      const player = t.player();
      if (!player) return

      player.onmouseenter = function (e) {
        h5Player._isFoucs = true;
      };
      player.onmouseleave = function (e) {
        h5Player._isFoucs = false;
      };
    },

    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
      const t = h5Player;
      const keyCode = event.keyCode;
      const key = event.key.toLowerCase();

      if (event.shiftKey && !event.ctrlKey && !event.altKey) {
        // 网页全屏
        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;
            });
          }
        }

        // 视频画面缩放相关事件
        const 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
        }

        const 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

      // 方向键右→:快进3秒
      if (keyCode === 39) {
        t.setCurrentTime(t.skipStep);
      }
      // 方向键左←:后退3秒
      if (keyCode === 37) {
        t.setCurrentTime(-t.skipStep);
      }

      // 方向键上↑:音量升高 1%
      if (keyCode === 38) {
        t.setVolume(0.01);
      }
      // 方向键下↓:音量降低 1%
      if (keyCode === 40) {
        t.setVolume(-0.01);
      }

      // 空格键:暂停/播放
      if (keyCode === 32) {
        t.switchPlayStatus();
      }

      // 按键X:减速播放 -0.1
      if (keyCode === 88) {
        if (player.playbackRate > 0) {
          t.setPlaybackRate(player.playbackRate - 0.1);
        }
      }
      // 按键C:加速播放 +0.1
      if (keyCode === 67) {
        if (player.playbackRate < 16) {
          t.setPlaybackRate(player.playbackRate + 0.1);
        }
      }
      // 按键Z:正常速度播放
      if (keyCode === 90) {
        const oldPlaybackRate = Number(player.playbackRate);
        const playbackRate = oldPlaybackRate === 1 ? t.lastPlaybackRate : 1;
        if (oldPlaybackRate !== 1) {
          t.lastPlaybackRate = oldPlaybackRate;
        }

        player.playbackRate = playbackRate;
        t.setPlaybackRate(player.playbackRate);
      }

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

      // 按键F:下一帧
      if (keyCode === 70) {
        if (window.location.hostname === 'www.netflix.com') {
          /* netflix 的F键是全屏的意思 */
          return
        }
        if (!player.paused) player.pause();
        player.currentTime += Number(1 / t.fps);
        t.tips('定位:下一帧');
      }
      // 按键D:上一帧
      if (keyCode === 68) {
        if (!player.paused) player.pause();
        player.currentTime -= Number(1 / t.fps);
        t.tips('定位:上一帧');
      }

      // 按键E:亮度增加%
      if (keyCode === 69) {
        t.filter.key[0] += 0.1;
        t.filter.key[0] = t.filter.key[0].toFixed(2);
        t.filter.setup();
        t.tips('图像亮度增加:' + parseInt(t.filter.key[0] * 100) + '%');
      }
      // 按键W:亮度减少%
      if (keyCode === 87) {
        if (t.filter.key[0] > 0) {
          t.filter.key[0] -= 0.1;
          t.filter.key[0] = t.filter.key[0].toFixed(2);
          t.filter.setup();
        }
        t.tips('图像亮度减少:' + parseInt(t.filter.key[0] * 100) + '%');
      }

      // 按键T:对比度增加%
      if (keyCode === 84) {
        t.filter.key[1] += 0.1;
        t.filter.key[1] = t.filter.key[1].toFixed(2);
        t.filter.setup();
        t.tips('图像对比度增加:' + parseInt(t.filter.key[1] * 100) + '%');
      }
      // 按键R:对比度减少%
      if (keyCode === 82) {
        if (t.filter.key[1] > 0) {
          t.filter.key[1] -= 0.1;
          t.filter.key[1] = t.filter.key[1].toFixed(2);
          t.filter.setup();
        }
        t.tips('图像对比度减少:' + parseInt(t.filter.key[1] * 100) + '%');
      }

      // 按键U:饱和度增加%
      if (keyCode === 85) {
        t.filter.key[2] += 0.1;
        t.filter.key[2] = t.filter.key[2].toFixed(2);
        t.filter.setup();
        t.tips('图像饱和度增加:' + parseInt(t.filter.key[2] * 100) + '%');
      }
      // 按键Y:饱和度减少%
      if (keyCode === 89) {
        if (t.filter.key[2] > 0) {
          t.filter.key[2] -= 0.1;
          t.filter.key[2] = t.filter.key[2].toFixed(2);
          t.filter.setup();
        }
        t.tips('图像饱和度减少:' + parseInt(t.filter.key[2] * 100) + '%');
      }

      // 按键O:色相增加 1 度
      if (keyCode === 79) {
        t.filter.key[3] += 1;
        t.filter.setup();
        t.tips('图像色相增加:' + t.filter.key[3] + '度');
      }
      // 按键I:色相减少 1 度
      if (keyCode === 73) {
        t.filter.key[3] -= 1;
        t.filter.setup();
        t.tips('图像色相减少:' + t.filter.key[3] + '度');
      }

      // 按键K:模糊增加 1 px
      if (keyCode === 75) {
        t.filter.key[4] += 1;
        t.filter.setup();
        t.tips('图像模糊增加:' + t.filter.key[4] + 'PX');
      }
      // 按键J:模糊减少 1 px
      if (keyCode === 74) {
        if (t.filter.key[4] > 0) {
          t.filter.key[4] -= 1;
          t.filter.setup();
        }
        t.tips('图像模糊减少:' + t.filter.key[4] + 'PX');
      }

      // 按键Q:图像复位
      if (keyCode === 81) {
        t.filter.reset();
        t.tips('图像属性:复位');
      }

      // 按键S:画面旋转 90 度
      if (keyCode === 83) {
        t.rotate += 90;
        if (t.rotate % 360 === 0) t.rotate = 0;
        player.style.transform = 'rotate(' + t.rotate + 'deg)';
        t.tips('画面旋转:' + t.rotate + '度');
      }

      // 按键回车,进入全屏
      if (keyCode === 13) {
        const isDo = TCC.doTask('fullScreen');
        if (!isDo && player._fullScreen_) {
          player._fullScreen_.toggle();
        }
      }

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

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

      /* 判断当前触发的快捷键是否已被注册 */
      function isRegister () {
        const list = taskConf.shortcuts.register;

        /* 当前触发的组合键 */
        const 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) => {
          const 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) {
      const isEditable = target.getAttribute && target.getAttribute('contenteditable') === 'true';
      const isInputDom = /INPUT|TEXTAREA|SELECT/.test(target.nodeName);
      return isEditable || isInputDom
    },

    /* 按键响应方法 */
    keydownEvent: function (event) {
      const t = h5Player;
      const keyCode = event.keyCode;
      const key = event.key.toLowerCase();
      const player = t.player();

      /* 处于可编辑元素中不执行任何快捷键 */
      if (t.isEditableTarget(event.target)) return

      /* shift+f 切换UA伪装 */
      if (event.shiftKey && keyCode === 70) {
        t.switchFakeUA();
      }

      /* 未用到的按键不进行任何事件监听 */
      const 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('禁用全局模式');
        }
      }

      /* 非全局模式下,不聚焦则不执行快捷键的操作 */
      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 = isInCrossOriginFrame() ? null : window.localStorage.getItem('_h5_player_play_progress_');
      if (!progressMap) {
        progressMap = {};
      } else {
        progressMap = JSON.parse(progressMap);
      }
      if (!player) {
        return progressMap
      } else {
        const keyName = window.location.href || player.src;
        if (progressMap[keyName]) {
          return progressMap[keyName].progress
        } else {
          return player.currentTime
        }
      }
    },
    /* 播放进度记录器 */
    playProgressRecorder: function (player) {
      const t = h5Player;
      clearTimeout(player._playProgressTimer_);
      function recorder (player) {
        player._playProgressTimer_ = setTimeout(function () {
          const progressMap = t.getPlayProgress();

          const keyName = window.location.href || player.src;
          const 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);
            const timestamp = timeList[0];

            /* 删除最早添加的记录项 */
            list.forEach(function (keyName) {
              if (progressMap[keyName].t === timestamp) {
                delete progressMap[keyName];
              }
            });
          }

          /* 记录当前播放进度 */
          progressMap[keyName] = {
            progress: player.currentTime,
            t: new Date().getTime()
          };

          /* 存储播放进度表 */
          !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_play_progress_', JSON.stringify(progressMap));

          /* 循环侦听 */
          recorder(player);
        }, 1000 * 2);
      }
      recorder(player);
    },
    /* 设置播放进度 */
    setPlayProgress: function (player, time) {
      if (!player) return
      const t = h5Player;
      const curTime = Number(t.getPlayProgress(player));
      if (!curTime || Number.isNaN(curTime)) return

      player.currentTime = curTime || player.currentTime;
      if (curTime > 3) {
        t.tips('为你恢复上次播放进度~');
      }
    },
    /**
     * 检测h5播放器是否存在
     * @param callback
     */
    detecH5Player: function () {
      const t = this;
      const playerList = t.getPlayerList();

      if (playerList.length) {
        console.log('检测到HTML5视频!');

        /* 单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 (isInIframe() && !isInCrossOriginFrame()) {
        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伪装的域名进行伪装 */
        const host = window.location.host;
        if (fakeConfig[host]) {
          t.setFakeUA(fakeConfig[host]);
        }
      } else {
        /* 检测是否存在H5播放器 */
        t.detecH5Player();
      }
    },
    load: false
  };

  /* 初始化任务配置中心 */
  TCC = h5PlayerTccInit(h5Player);

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

    /* 检测到有视频标签就进行初始化 */
    ready('video', function () {
      h5Player.init();
    });

    /* 检测shadow dom 下面的video */
    document.addEventListener('addShadowRoot', function (e) {
      const shadowRoot = e.detail.shadowRoot;
      ready('video', function (element) {
        h5Player.init();
      }, shadowRoot);
    });

    if (isInCrossOriginFrame()) {
      window._h5PlayerForDebug_ = h5Player;
      debugMsg('当前处于跨域受限的Iframe中,h5Player相关功能可能无法正常开启');
    } else {
      window.top._h5PlayerForDebug_ = h5Player;
    }
  } catch (e) {
    console.error('h5player:', e);
  }

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