HTML5 Video Player Enhance

To enhance the functionality of HTML5 Video Player (h5player) supporting all websites using shortcut keys similar to PotPlayer.

Verze ze dne 17. 10. 2019. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         HTML5 Video Player Enhance
// @version      2.8.0b11
// @description  To enhance the functionality of HTML5 Video Player (h5player) supporting all websites using shortcut keys similar to PotPlayer.
// @author       CY Fung
// @match        http://*/*
// @match        https://*/*
// @run-at       document-start
// @require https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js
// @namespace https://greatest.deepsurf.us/users/371179
// ==/UserScript==
(function () {
    'use strict';
    // Your code here...



    // fullscreen and pointerLock   buggy in shadowRoot

    let _debug_h5p_logging_ = false;

    try {
        _debug_h5p_logging_ = +window.localStorage.getItem('_h5_player_sLogging_') > 0
    } catch (e) {

    }

    const str_postMsgData = '__postMsgData__'

    const dround = function (x) {
        return ~~(x + .5)
    }

    const _checkActiveNode = function (activeElm, player) {

        if (activeElm == player) {
            return true;
        }

        let _checkingPass = false;
        let {
            layoutBox,
            wPlayer
        } = h5Player.getLayoutBox(player);
        if (layoutBox && layoutBox.parentNode && layoutBox.contains(activeElm)) {
            let rpid = player.getAttribute('_h5ppid') || "NULL";
            let actionBox = layoutBox.parentNode.querySelector(`[_h5p_actionbox_="${rpid}"]`); //the box can be layoutBox
            if (actionBox && actionBox.contains(activeElm)) _checkingPass = true;
        }

        return _checkingPass
    }

    const jsonStringify_replacer = function (key, val) {
        // convert RegExp to string
        if (val && (val instanceof Element || val instanceof Document)) {
            return toString.call(val);
        } else if (name === 'st12131r') { //
            return undefined; // remove from result
        } else {
            return val; // return as is
        }
    };

    const jsonParse = function () {
        let res = null;
        try {
            res = JSON.parse.apply(this, arguments)
        } catch (e) {

        }
        return res;
    }
    const jsonStringify = function () {
        let res = null;
        let value = arguments[0];
        let replacer = arguments[1];
        replacer = jsonStringify_replacer;
        let space = arguments[2];
        let arg = [value, replacer, space]
        if (arguments.length <= 2) arg.length = 2;
        try {
            res = JSON.stringify.apply(this, arg)
        } catch (e) {

        }
        return res;
    }

    function _postMsg() {
        //async is needed. or error handling for postMessage
        const [win, tag, ...data] = arguments;
        if (typeof tag == 'string') {
            let postMsgObj = {
                tag,
                passing: true,
                winOrder: _postMsg.a
            }
            try {
                var k = 'msg-' + (+new Date)
                win.document[str_postMsgData] = win.document[str_postMsgData] || {}
                win.document[str_postMsgData][k] = data; //direct
                postMsgObj.str = k;
                postMsgObj.stype = 1;
            } catch (e) {}
            if (!postMsgObj.stype) {
                postMsgObj.str = jsonStringify({
                    d: data
                })
                if (postMsgObj.str && postMsgObj.str.length) postMsgObj.stype = 2;
            }
            if (!postMsgObj.stype) {
                postMsgObj.str = "" + data;
                postMsgObj.stype = 0;
            }
            win.postMessage(postMsgObj, '*');
        }

    }

    function postMsg() {
        let win = window;
        var a = 0;
        while (win = win.parent) {
            _postMsg.a = ++a;
            _postMsg(win, ...arguments)
            if (win == top) break;
        }
    }

    let h5Player;

    const SHIFT = 1;
    const CTRL = 2;
    const ALT = 4;
    const TERMINATE = 0x842;

    const _sVersion_ = 1817;

    const getRoot = (elm) => elm.getRootNode instanceof Function ? elm.getRootNode() : (elm.ownerDocument || null);

    const isShadowRoot = (elm) => (elm && ('host' in elm)) ? elm.nodeType == 11 && !!elm.host && elm.host.nodeType == 1 : null; //instanceof ShadowRoot

    //console.log(Object.keys(window).filter(k=>window[k] instanceof Function && window[k].toString().indexOf('native code')<=0))
    //console.log(sha256('123'))

    // built-in hash
    //https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
    //const text = 'An obscure body in the S-K System, your majesty. The inhabitants refer to it as the planet Earth.';
    /*
    async function digestMessage(message) {

    }
    */

    async function digestMessage(message) {
        return window.sha256(message)
    }

    //const digestHex = await digestMessage(text);
    //console.log(digestHex);

    // function evalcode() {
    // [...document.querySelectorAll('*')].filter(x => !!x._listeners).filter(elm => elm.contains(document.querySelector('video')) || document.querySelector('video').contains(elm)).forEach(elm => elm.addEventListener('click', function () {
    // this.setAttribute('tabindex', '1')
    // }))
    // }

    //https://gist.github.com/joyrexus/7304146
    // requestAnimationFrame() shim by Paul Irish
    let requestAnimationFrame = (function () {
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function ( /* function */ callback, /* DOMElement */ element) {
            return window.setTimeout(callback, 1000 / 60);
        };
    })();

    let cancelAnimationFrame = (function () {
        return window.cancelAnimationFrame ||
            window.webkitCancelAnimationFrame ||
            window.webkitCancelRequestAnimationFrame ||
            window.mozCancelRequestAnimationFrame ||
            window.oCancelRequestAnimationFrame ||
            window.msCancelRequestAnimationFrame ||
            clearInterval;
    })();

    function lowerKeyCode(keyCode) {
        if (keyCode >= 65 && keyCode <= 90) {
            keyCode += 32;
        }
        return keyCode
    }

    function whichTransitionEvent(type) {
        if (whichTransitionEvent['_result_' + type]) return whichTransitionEvent['_result_' + type]
        var t,
            el = document.createElement("fakeelement");

        const capital = (x) => x[0].toUpperCase() + x.substr(1);
        const capitalType = capital(type);

        const transitions = {
            [type]: `${type}end`,
            [`O${capitalType}`]: `o${capitalType}End`,
            [`Moz${capitalType}`]: `${type}end`,
            [`Webkit${capitalType}`]: `webkit${capitalType}End`,
            [`MS${capitalType}`]: `MS${capitalType}End`
        }

        for (t in transitions) {
            if (el.style[t] !== undefined) {
                return (whichTransitionEvent['_result_' + type] = transitions[t]);
            }
        }
    }

    Element.prototype.__matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function (s) {
        var matches = ('getRootNode' in this ? this.getRootNode() : this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
        while (--i >= 0 && matches.item(i) !== this) {}
        return i > -1;
    };

    Element.prototype.__requestPointerLock = Element.prototype.requestPointerLock ||
        Element.prototype.mozRequestPointerLock ||
        Element.prototype.webkitRequestPointerLock;

    // Ask the browser to release the pointer
    Document.prototype.__exitPointerLock = Document.prototype.exitPointerLock ||
        Document.prototype.mozExitPointerLock ||
        Document.prototype.webkitExitPointerLock;

    const __resizeListeners__ = {};
    let __resizerCount__ = 0;
    class ResizeODM {
        constructor() {
            let rm = this;
            __resizerCount__++;
            let rpid = "rpid-" + __resizerCount__;
            __resizeListeners__[rpid] = [];

            rm._resizer_listeners = __resizeListeners__[rpid];

            var odm = document.createElement('object');
            odm.setAttribute('_resizer_odm_', rpid);
            odm.setAttribute('style', 'display: block; position: absolute; top: -300vh; left: -300vw; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;');
            odm.onload = ResizeODM.objectLoad;
            odm.type = 'text/html';
            odm.data = 'about:blank';
            rm.odm = odm;
            rm.rpid = rpid;
            rm.odm.__rm__ = rm;
        }

        static find(rsElm) {
            if (!rsElm) return null;
            let odm = Array.prototype.slice.call(rsElm.querySelectorAll('object[_resizer_odm_]')).filter(elm => elm.parentNode == rsElm)[0];
            if (!odm) return null;

            return odm.__rm__ || null;

        }

        static resizeListener(e) {
            let odv = e.target || e.srcElement;
            let rm = odv._resizer_rm;
            if (rm.__resizeRAF__) cancelAnimationFrame(rm.__resizeRAF__);
            rm.__resizeRAF__ = requestAnimationFrame(function () {
                rm.__resizeRAF__ = 0;
                rm._resizer_listeners.forEach(fn => fn.call(rm, e));
            });
        }

        static objectLoad(e) {
            let odm = this;
            let rm = odm.__rm__;
            let odv = odm.contentDocument.defaultView
            odv._resizer_rm = rm;
            rm.odv = odv;
            odv.onresize = ResizeODM.resizeListener;
        }

        resizeElement() {
            return this.odm.parentNode;
        }

        // ResizeODM.relativeParent(rsElm);

        relativeParent(rsElm, existingnode = null) {
            let odm = this.odm;
            rsElm = rsElm || odm.parentNode;
            let rpid = this.rpid;
            //if (getComputedStyle(rsElm).position == 'static') rsElm.style.position = 'relative';
            rsElm.insertBefore(odm, existingnode);
        }

        listen(fn) {
            this._resizer_listeners.push(fn);
        }

        unlisten(fn) {
            this._resizer_listeners.splice(this._resizer_listeners.indexOf(fn), 1);
        }

        remove() {
            this._resizer_listeners.length = 0;
            this.odv.onresize = null;
            this.odm.onload = null;
        }

    }

    //let __activeElementsToggles=[];

    function isInOperation(elm) {

        let elmInFocus = elm || document.activeElement;
        if (!elmInFocus) return false;

        let res1 = elmInFocus.__matches('a[href],link[href],button,input:not([type="hidden"]),select,textarea,iframe,frame,menuitem,[draggable],[contenteditable]')

        return res1

    }

    class SimEvent extends Event {
        constructor(typeArg, eventInit, customType) {
            super(typeArg, eventInit);
            this.__customType = customType
        }
        get type() {
            return this.__customType
        }
    }

    const _evtOB_create = function (_EVT, d) {
        let keys = Object.keys(_EVT.prototype);
        let res = function Event(m) {

            let o = this
            keys.forEach(k => (k in m && !(k in o)) ? o[k] = m[k] : null);
            Object.assign(o, d);

        }
        return res

    }
    _evtOB_create._timeupdate = function () {
        if (_evtOB_create.__timeupdate) return _evtOB_create.__timeupdate;
        let res = _evtOB_create(Event, {
            isTrusted: true,
            type: 'timeupdate'
        })
        return _evtOB_create.__timeupdate = res;

    }
    const sim_arg_1_fn = function (f, evtOB) {
        return function (e) {
            let o = new evtOB(e);
            let arg = Array.prototype.slice.call(arguments)
            arg[0] = o
            //console.log('success',[...arg])
            return f.apply(this, arg)
        }
    }

    const fn_toString = (f, n = 50) => {
        let s = (f + "");
        if (s.length > 2 * n + 5) {
            s = s.substr(0, n) + ' ... ' + s.substr(-n);
        }
        return s
    };

    //let p={};for(let k in x) if(! (k in document.body))p[k]=x[k];p

    const _ell_timeupdatefs = [];

    let _endlessloop = null;

    class EndlessLoop {
        constructor() {
            this.activeLoopsCount = 0;
            this.loops = [];
            this.cid = 0;
            this._loop = () => {
                if (!this.cid) return; //cancelled
                this.loops.forEach(loop => loop.opt.looping ? loop.fn(loop.opt) : null);
                this.cid = requestAnimationFrame(this._loop);
            }
        }

        loopStart() {
            this.looping = true;
            this.cid = requestAnimationFrame(this._loop);
        }
        loopStop() {
            if (this.cid) cancelAnimationFrame(this.cid);
            this.cid = 0;
            this.looping = false;
        }
        append(fn) {
            const opt = new EndlessLoopOpts(this);
            this.loops.push({
                fn,
                opt
            });
            return opt;
        }
    }

    class EndlessLoopOpts {
        constructor(ell) {
            this._looping = false;
            this._ell = ell;
        }
        _loop() {
            this._ell.loops.some(loop => loop.opt === this && loop.opt.looping ? (loop.fn(loop.opt), true) : null);
        }
        get looping() {
            return this._looping;
        }
        loopingStart() {
            if (this._looping === false) {
                this._looping = true;
                this._ell.activeLoopsCount++;
                if (this._ell.activeLoopsCount == 1) this._ell.loopStart();
            }
        }
        loopingStop() {
            if (this._looping === true) {
                this._looping = false;
                this._ell.activeLoopsCount--;
                if (this._ell.activeLoopsCount == 0) this._ell.loopStop();
            }
        }
    }

    const UA = {};
    const tool = {};

    const isIframe = (window.top !== window.self && window.top && window.self);

    function consoleLog() {
        if (!_debug_h5p_logging_) return;
        if (isIframe) postMsg('consoleLog', ...arguments);
        else console.log.apply(console, arguments);
    }

    function consoleLogF() {
        if (isIframe) postMsg('consoleLog', ...arguments);
        else console.log.apply(console, arguments);
    }

    const Store = {
        prefix: '_h5_player',
        save: function (k, v) {
            if (!Store.available()) return false;
            if (typeof v != 'string') return false;
            Store.LS.setItem(Store.prefix + k, v)
            let sk = fn_toString(k + "", 30);
            let sv = fn_toString(v + "", 30);
            consoleLog(`localStorage Saved "${sk}" = "${sv}"`)
            return true;

        },
        read: function (k) {
            if (!Store.available()) return false;
            let v = Store.LS.getItem(Store.prefix + k)
            let sk = fn_toString(k + "", 30);
            let sv = fn_toString(v + "", 30);
            consoleLog(`localStorage Read "${sk}" = "${sv}"`);
            return v;

        },
        remove: function (k) {

            if (!Store.available()) return false;
            Store.LS.removeItem(Store.prefix + k)
            let sk = fn_toString(k + "", 30);
            consoleLog(`localStorage Removed "${sk}"`)
            return true;
        },
        clearInvalid: function (sVersion) {
            if (!Store.available()) return false;

            //let sVersion=1814;
            if (+Store.read('_sVersion_') < sVersion) {
                Object.keys(localStorage).filter(s => s.indexOf(Store.prefix) === 0).forEach(key => window.localStorage.removeItem(key))
                Store.save('_sVersion_', sVersion + '')
                return 2;
            }
            return 1;

        },
        available: function () {
            if (Store.LS) return true;
            if (!window) return false;
            const localStorage = window.localStorage;
            if (!localStorage) return false;
            if (typeof localStorage != 'object') return false;
            if (!('getItem' in localStorage)) return false;
            if (!('setItem' in localStorage)) return false;
            Store.LS = localStorage;
            return true;

        }

    }

    const domTool = {
        nopx: (x) => +x.replace('px', ''),
        cssWH: function (m) {
            let r = getComputedStyle(m, null);
            let c = (x) => +x.replace('px', '');
            return {
                w: m.offsetWidth || c(r.width),
                h: m.offsetHeight || c(r.height)
            }
        },
        _cssWH: function (m, r) {
            let c = (x) => +x.replace('px', '');
            return {
                w: m.offsetWidth || c(r.width),
                h: m.offsetHeight || c(r.height)
            }
        },
        _isActionBox_1: function (vEl, pEl) {

            let vElCSS = domTool.cssWH(vEl);
            let vElCSSw = vElCSS.w;
            let vElCSSh = vElCSS.h;

            let vElx = vEl;
            let res = [];
            let mLevel = 0;
            if (vEl && pEl && vEl != pEl && pEl.contains(vEl)) {
                while (vElx && vElx != pEl) {
                    vElx = vElx.parentNode;
                    let vElx_css = null;
                    if (isShadowRoot(vElx)) {} else {
                        vElx_css = getComputedStyle(vElx, null);
                        let vElx_wp = domTool.nopx(vElx_css.paddingLeft) + domTool.nopx(vElx_css.paddingRight)
                        vElCSSw += vElx_wp
                        let vElx_hp = domTool.nopx(vElx_css.paddingTop) + domTool.nopx(vElx_css.paddingBottom)
                        vElCSSh += vElx_hp
                    }
                    res.push({
                        level: ++mLevel,
                        padW: vElCSSw,
                        padH: vElCSSh,
                        elm: vElx,
                        css: vElx_css
                    })

                }
            }

            // in the array, each item is the parent of video player
            res.vEl_cssWH = vElCSS

            return res;

        },
        _isActionBox: function (vEl, walkRes, pEl_idx) {

            function absDiff(w1, w2, h1, h2) {
                let w = (w1 - w2),
                    h = h1 - h2;
                return [(w > 0 ? w : -w), (h > 0 ? h : -h)]
            }

            function midPoint(rect) {
                return {
                    x: (rect.left + rect.right) / 2,
                    y: (rect.top + rect.bottom) / 2
                }
            }

            let parentCount = walkRes.length;
            if (pEl_idx >= 0 && pEl_idx < parentCount) {} else {
                return;
            }
            let pElr = walkRes[pEl_idx]
            if (!pElr.css) {
                //shadowRoot
                return true;
            }

            let pEl = pElr.elm;

            //prevent activeElement==body
            let pElCSS = domTool._cssWH(pEl, pElr.css);
            //let vElCSS=walkRes.vEl_cssWH;

            //check prediction of parent dimension
            let d1v = absDiff(pElCSS.w, pElr.padW, pElCSS.h, pElr.padH)

            let d1x = d1v[0] < 10
            let d1y = d1v[1] < 10;

            if (d1x && d1y) return true; //both edge along the container   -  fit size
            if (!d1x && !d1y) return false; //no edge along the container     -  body contain the video element, fixed width&height

            //case: youtube video fullscreen

            //check centre point

            let pEl_rect = pEl.getBoundingClientRect()
            let vEl_rect = vEl.getBoundingClientRect()

            let pEl_center = midPoint(pEl_rect)
            let vEl_center = midPoint(vEl_rect)

            let d2v = absDiff(pEl_center.x, vEl_center.x, pEl_center.y, vEl_center.y);

            let d2x = d2v[0] < 10;
            let d2y = d2v[1] < 10;

            return (d2x && d2y);

        },
        getRect: function (element) {
            let rect = element.getBoundingClientRect();
            let scroll = domTool.getScroll();
            return {
                pageX: rect.left + scroll.left,
                pageY: rect.top + scroll.top,
                screenX: rect.left,
                screenY: rect.top
            };
        },
        isHalfFullClient: function (element) {
            var client = domTool.getClient();
            var rect = domTool.getRect(element);
            if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) || (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
                if (Math.abs(element.offsetWidth / 2 + rect.screenX - client.width / 2) < 10 && Math.abs(element.offsetHeight / 2 + rect.screenY - client.height / 2) < 10) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        },
        isAllFullClient: function (element) {
            var client = domTool.getClient();
            var rect = domTool.getRect(element);
            if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) && (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
                return true;
            } else {
                return false;
            }
        },
        getScroll: function () {
            return {
                left: document.documentElement.scrollLeft || document.body.scrollLeft,
                top: document.documentElement.scrollTop || document.body.scrollTop
            };
        },
        getClient: function () {
            return {
                width: document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth,
                height: document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight
            };
        },
        addStyle: //GM_addStyle,
        function (css, head) {
            if (!head) {
                let _doc = document.documentElement;
                head = _doc.querySelector('head') || _doc.querySelector('html') || _doc;
            }
            let doc = head.ownerDocument;
            let style = doc.createElement('style');
            style.type = 'text/css';
            let node = doc.createTextNode(css);
            style.appendChild(node);
            head.appendChild(style);
            //console.log(document.head,style,'add style')
            return style;
        },
        eachParentNode: function (dom, fn) {
            let parent = dom.parentNode
            while (parent) {
                let isEnd = fn(parent, dom)
                parent = parent.parentNode
                if (isEnd) {
                    break
                }
            }
        },

        hideDom: function hideDom(selector) {
            let dom = document.querySelector(selector)
            if (dom) {
                requestAnimationFrame(function () {
                    dom.style.opacity = 0;
                    dom.style.transform = 'translate(-9999px)';
                    dom = null;
                })
            }
        }
    };

    const handle = {
        volume_changed: function () {

            if (this.volume >= 0) {} else {
                return;
            }
            let cVol = this.volume;
            let cMuted = this.muted;

            if (cVol === this._volume_p && cMuted === this._muted_p) {
                // nothing changed
            } else if (cVol === this._volume_p && cMuted !== this._muted_p) {
                // muted changed
            } else { // cVol != pVol

                // only volume changed

                let shallShowTips = this._volume >= 0; //prevent initialization

                if (!cVol) {
                    //if(!this.muted) h5Player.tips('Mute: On');
                    this.muted = true;
                } else if (cMuted) {
                    this.muted = false;
                    this._volume = cVol;
                    //h5Player.tips('Mute: On');

                } else if (!cMuted) {
                    this._volume = cVol;
                }
                consoleLog('volume changed')

                let player = this;
                let t = h5Player;

                if (shallShowTips)
                    t.tips('Volume: ' + dround(player.volume * 100) + '%', undefined, 3000)

            }

            this._volume_p = cVol
            this._muted_p = cMuted

        },
        player_videoLoaded: function () {
            consoleLog('video size', this.videoWidth + ' x ' + this.videoHeight);

            let t = h5Player;
            let player = this
            let vpid = player.getAttribute('_h5ppid') || null;
            if (!vpid || !player.currentSrc) return;

            if (t.srcList[vpid] != player.currentSrc) {
                t.srcList[vpid] = player.currentSrc;
                t.videoSrcFound(player);
                t._actionBoxSet(player);
            }

            if (!player.__video_loaded__) {
                player.__video_loaded__ = 1;

                let video = player;

                video.addEventListener('playing', handle.player_playTriggering) /* 播放器開始播放的時候重新指向實例 */
                video.addEventListener('pause', handle.srcLoaded_pause);
                video.addEventListener("volumechange", handle.volume_changed);
                video.addEventListener('videoparentresize', handle.player_DOMresize);

                //_action_eventsBind(video,'addEventListener')
                //_action_eventsBind(_actionBoxSet(video),'addEventListener')
            }

        },
        player_DOMresize: function (evt) {
            consoleLog('videoparentresize')
            //fix

            requestAnimationFrame(function () {
                let player = evt.detail.player
                let t = h5Player
                let tipsSelector = '#' + (player.getAttribute('_h5player_tips') || t.tipsClassName)
                let tipsDom = getRoot(player).querySelector(tipsSelector) || null

                if (tipsDom) {
                    h5Player.fixNonBoxingVideoTipsPosition(tipsDom, player);
                }
            });

        },
        player_mouseEnter: function (evt) {
            let player = evt.target;
            if (player.nodeName != "VIDEO") player = player.querySelector('video[_h5ppid]');
            if (player)
                h5Player._actionBoxSet(player)
        },
        player_mouseDown: function (evt) {
            let player = this; //evt.target may be something else
            if (player.nodeName != "VIDEO") player = player.querySelector('video[_h5ppid]');
            if (!player) return;
            let {
                layoutBox,
                wPlayer
            } = h5Player.getLayoutBox(player);
            if (layoutBox && layoutBox.contains(evt.target)) {
                h5Player.makeFocus(player, evt)
            }
        },
        wheel_tunevolume: function (evt) {

            //consoleLogF('E',evt.target)
            //if (evt.deltaMode!==0) return;  // consider DOM_DELTA_PIXEL only;
            if(!evt.shiftKey)return;
            if (evt.deltaY) {
                let t = h5Player;
                let player = t.player();
                if (!t || !player) return;

                //consoleLogF('ABC01', player.outerHTML)
                //var aabox=t._actionBoxSet(player);
                //consoleLogF('ABC02', aabox.outerHTML)
                //consoleLogF('ABC03', aabox.offsetHeight, aabox.offsetWidth, player.offsetHeight, player.offsetWidth)
                //if (!_checkActiveNode(getRoot(player).activeElement, player)) return;
                if (evt.deltaY > 0) {

                    if ((player.muted && player.volume === 0) && player._volume > 0) {

                        player.muted = false;
                        player.volume = player._volume;
                    } else if (player.muted && (player.volume > 0 || !player._volume)) {
                        player.muted = false;
                    }
                    t.tuneVolume(-0.05)

                    event.stopPropagation()
                    event.preventDefault()
                    return false

                } else if (evt.deltaY < 0) {

                    if ((player.muted && player.volume === 0) && player._volume > 0) {

                        player.muted = false;
                        player.volume = player._volume;
                    } else if (player.muted && (player.volume > 0 || !player._volume)) {
                        player.muted = false;
                    }
                    t.tuneVolume(+0.05)

                    event.stopPropagation()
                    event.preventDefault()
                    return false

                }
            }
        },
        player_mouseLeave: function (e) {
            // h5Player._isFoucs = false
            // consoleLog('player mouse leave')

        },
        player_playTriggering: function (evt) {

            h5Player._actionBoxSet(evt.target)

            h5Player.playerInstance = evt.target
            h5Player.onVideoTriggering()

            //by default, when the video start playing, the layoutbox can get the focus and activate the keyboard control
            //let {
            //    layoutBox,
            //    wPlayer
            //} = h5Player.getLayoutBox(evt.target);
            //if (layoutBox && layoutBox.hasAttribute('tabindex')) layoutBox.focus();

            return handle.srcLoaded_playing.call(this, evt)

        },
        player_dblClick: function (evt) {

            consoleLog('dblclick', this, evt.target)

            if(this.nodeName=='VIDEO'&&evt.target.nodeName=='VIDEO' &&this.getAttribute('_h5p_actionbox_')){return;}
            if(document.readyState!="complete")return;

            let elm = (evt.target != this && this.contains(evt.target)) ? this : evt.target;
            //  www.tucao.one   ;  evt.target = bullet curtain;  this = actionbox

            let vpid = elm.getAttribute('_h5p_actionbox_') || elm.getAttribute('_h5ppid') || null;
            if (!vpid) return;
            let player = getRoot(elm).querySelector(`[_h5ppid="${vpid}"]`);
            h5Player._actionBoxSet(player)
            h5Player.playerInstance = player
            h5Player.onVideoTriggering()

            //h5Player.makeFocus(player, evt)

            //by default, when the video start playing, the layoutbox can get the focus and activate the keyboard control
            // let shallFocus = false;
            // if (document.activeElement == null) shallFocus = true;
            // let {
            // layoutBox,
            // wPlayer
            // } = h5Player.getLayoutBox(player);
            // if (layoutBox) {
            // if (layoutBox == document.activeElement || layoutBox.contains(document.activeElement)) shallFocus = false;
            // if (shallFocus && layoutBox.hasAttribute('tabindex')) layoutBox.focus();
            // }

            //try{
            h5Player.callFullScreenBtn();
            //}catch(e){}

            evt.stopPropagation()
            evt.preventDefault()
            return false

        },
        doc_focusout: function (e) {
            let doc = this;
            h5Player.focusFxLock = true;
            requestAnimationFrame(function () {
                h5Player.focusFxLock = false;
                if (!h5Player.enable) h5Player.tips(false);
                else
                    if (!doc.hasFocus() && h5Player.player() && !h5Player.isLostFocus) {
                        h5Player.isLostFocus = true;
                        consoleLog('doc.focusout')
                        h5Player.tips('focus is lost', -1);
                    }
            });
        },
        doc_focusin: function (e) {
            let doc = this;
            if (h5Player.focusFxLock) return;
            requestAnimationFrame(function () {

                if (h5Player.focusFxLock) return;
                if (!h5Player.enable) h5Player.tips(false);
                else
                    if (doc.hasFocus() && h5Player.player() && h5Player.isLostFocus) {
                        h5Player.isLostFocus = false;
                        consoleLog('doc.focusin')
                        h5Player.tips(false);

                    }
            });
        },

        win_receiveMsg: async function (e) {
            let tag, ed;
            if (typeof e.data == 'object' && typeof e.data.tag == 'string') {
                tag = e.data.tag;
                ed = e.data
            } else {
                return;
            }
            let msg = null,
                success = 0;
            switch (tag) {
                case 'consoleLog':
                    var msg_str = ed.str;
                    var msg_stype = ed.stype;
                    if (msg_stype === 1) {
                        msg = (document[str_postMsgData] || {})[msg_str] || [];
                        success = 1;
                    } else if (msg_stype === 2) {
                        msg = jsonParse(msg_str);
                        if (msg && msg.d) {
                            success = 2;
                            msg = msg.d;
                        }
                    } else {
                        msg = msg_str
                    }
                    var p = (ed.passing && ed.winOrder) ? [' | from win-' + ed.winOrder] : [];
                    if (success) {
                        console.log(...msg, ...p)
                        //document[ed.data]=null;   // also delete the information
                    } else {
                        console.log('msg--', msg, ...p, ed);
                    }
                    break;

            }
        },
        timeupdatef_ell: async function (opts) {
            let video = opts.video;
            let time = video.currentTime;
            if (time !== opts.lastTime) {
                opts.lastTime = time;
                video.dispatchEvent(opts.evt);
            }
        },
        playbackELL: async(opts) => {

            let qTime = +new Date;
            if (qTime >= opts.pTime) {
                opts.pTime = qTime + opts.timeDelta; //prediction of next Interval
                opts.playbackRecord()
            }

        },
        playbackRecord: async function () {

            //this refer to endless's opts
            let player = this.player;
            let t = h5Player;

            let _uid = this.player_uid; //_h5p_uid_encrypted
            if (!_uid) return;

            let shallSave = true;
            let currentTimeToSave = ~~player.currentTime;

            if (this._lastSave == currentTimeToSave) shallSave = false;

            if (shallSave) {

                this._lastSave = currentTimeToSave

                //console.log('aasas',this.player_uid, shallSave, '_play_progress_'+_uid, currentTimeToSave)

                Store.save('_play_progress_' + _uid, jsonStringify({
                    't': currentTimeToSave
                }))

            }

        },
        pr_updateUID: function () {

            //this refer to endless's opts

            let player = this.player;

            let t = h5Player
            let _uid = player.getAttribute('_h5p_uid_encrypted') || ''
            if (!_uid) return false;
            this.player_uid = _uid;

            return true;

        },

        srcLoaded_playing: function () {

            if (!h5Player.enable) return h5Player.tips(false);
            let player = this;
            if (player._isThisPausedBefore_) consoleLog('resumed')
            let _pausedbefore_ = player._isThisPausedBefore_

            if(player.playpause_cid){
                clearTimeout(player.playpause_cid);
                player.playpause_cid=0;
            }
            let _last_paused = player._last_paused
            player._last_paused=player.paused
            if(_last_paused===!player.paused){
                player.playpause_cid=setTimeout(()=>{
                    if(player.paused===!_last_paused && !player.paused && _pausedbefore_){
                        h5Player.tips('Playback resumed', undefined, 2500)
                    }
                },90)
            }

            /* 播放的時候進行相關同步操作 */

            if (!player._record_continuous) {

                /* 同步之前設定的播放速度 */
                h5Player.setPlaybackRate()

                if (!_endlessloop) {
                    _endlessloop = new EndlessLoop();
                }
                player._record_continuous = _endlessloop.append(handle.playbackELL);
                player._record_continuous._lastSave = -999;

                player._record_continuous.timeDelta = 2000;
                player._record_continuous.player = player
                player._record_continuous.playbackRecord = handle.playbackRecord;
                player._record_continuous.updateUID = handle.pr_updateUID;

                player._record_continuous.playingWithRecording = function () {

                    let player = this.player;

                    if (!player.paused && this.updateUID()) {

                        if (!this.looping) {

                            this.pTime = 0;
                            this.loopingStart();
                        }

                    }

                }

            } else {
                //just change the src
                // player._record_continuous.updateUID();

            }

            player._record_continuous.playingWithRecording(player); //try to start recording

            _ell_timeupdatefs.forEach(opts => opts.loopingStart());

            player._isThisPausedBefore_ = false;

        },
        srcLoaded_pause: function () {

            if (!h5Player.enable) return h5Player.tips(false);
            let player = this;
            consoleLog('pause')
            player._isThisPausedBefore_ = true;
            //requestAnimationFrame(() => h5Player.tips('Playback paused', undefined, 2500))


            let _last_paused = player._last_paused
            player._last_paused=player.paused
            if(player.playpause_cid){
                clearTimeout(player.playpause_cid);
                player.playpause_cid=0;
            }
            if(_last_paused===!player.paused){
                player.playpause_cid=setTimeout(()=>{
                    if(player.paused===!_last_paused && player.paused){
                        h5Player.tips('Playback paused', undefined, 2500)
                    }
                },90)
            }


            if (player._record_continuous && player._record_continuous.looping) {
                player._record_continuous.playbackRecord(); //playbackRecord once before stopping  //handle.playbackRecord;
                player._record_continuous.loopingStop();
            }

            _ell_timeupdatefs.forEach(opts => opts.loopingStop());

        },
    };

    const _action_eventsBind = function (elm, evlistener) {

        if (elm._action_eventsBind !== evlistener) {
            elm._action_eventsBind = evlistener;

            elm[evlistener]('mouseenter', handle.player_mouseEnter)
            elm[evlistener]('mousedown', handle.player_mouseDown)
            elm[evlistener]('dblclick', handle.player_dblClick,true)
            elm[evlistener]('wheel', handle.wheel_tunevolume);

        }

    }

    const pictureInPicture = function (videoElm) {
        if (document.pictureInPictureElement) document.exitPictureInPicture();
        else {
            ('requestPictureInPicture' in videoElm) ? videoElm.requestPictureInPicture(): h5Player.tips('PIP is not supported.');
        }

    }

    const TCC_items = [

        {
            '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
                    consoleLog(event, player)
                }
            },
            'include': ['||demo.demo^'],
            'exclude': [],
        }, {
            // 'webFullScreen': 'button.ytp-size-button',
            'fullScreen': 'button.ytp-fullscreen-button',
            'include': ['||youtube.com^'],
        }, {
            'fullScreen': 'button.button-nfplayerFullscreen',
            'addCurrentTime': 'button.button-nfplayerFastForward',
            'subtractCurrentTime': 'button.button-nfplayerBackTen',
            'include': ['||netflix.com^'],
        }, {
            'fullScreen': '[data-text="进入全屏"],[data-text="進入全屏"]',
            'webFullScreen': '[data-text="退出全屏",[data-text="退出全屏]"]',
            'autoPlay': '.bilibili-player-video-btn-start',
            'switchPlayStatus': '.bilibili-player-video-btn-start',
            'include': ['||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',
            'include': ['||live.bilibili.com^']
        }, {
            'fullScreen': '.iqp-btn-fullscreen',
            'webFullScreen': '.iqp-btn-webscreen',
            'init': function (h5Player, taskConf) {
                // 隱藏水印
                domTool.hideDom('.iqp-logo-box')
                // 移除暫停廣告
                domTool.addStyle('div[templatetype="common_pause"]{ display:none }')
            },
            'include': ['||iqiyi.com^']
        }, {
            'fullScreen': '.control-fullscreen-icon',
            'init': function (h5Player, taskConf) {
                // 隱藏水印
                domTool.hideDom('.youku-layer-logo')
            },
            'include': ['||youku.com^']
        }, {
            'fullScreen': 'button.Fullscreen',
            'include': ['||ted.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.tuneCurrentTime(0.1, -1)
                        h5Player.setPlaybackRate(targetSpeed, -1)
                        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) {
                // 隱藏水印
                domTool.hideDom('.txp-watermark')
            },
            'include': ['||v.qq.com^']
        }, {
            'fullScreen': function (h5Player, taskConf) {
                //h5Player.playerInstance.parentNode.querySelector('.vjs-fullscreen-control').click()
                h5Player.player().parentNode.querySelector('.vjs-fullscreen-control').click();
            },
            'include': ['||pan.baidu.com^']
        }, {
            fullScreen: function (h5Player, taskConf) {
                const player = h5Player.player();
                let {
                    layoutBox,
                    wPlayer
                } = h5Player.getLayoutBox(player)
                const container = layoutBox.toParentContains('div[title*="窗口全屏"]');
                if (container) {
                    if (player._isFullScreen_) {

                        container.querySelector('div[title="退出窗口全屏"]').click();
                    } else {
                        container.querySelector('div[title="窗口全屏"]').click();
                    }
                    player._isFullScreen_ = !player._isFullScreen_;
                    return true
                }
            },
            webFullScreen: function (h5Player, taskConf) {
                const player = h5Player.player();
                let {
                    layoutBox,
                    wPlayer
                } = h5Player.getLayoutBox(player)
                const container = layoutBox.toParentContains('div[title*="网页全屏"]');
                if (container) {
                    if (player._isWebFullScreen_) {
                        container.querySelector('div[title="退出网页全屏"]').click();
                    } else {
                        container.querySelector('div[title="网页全屏"]').click();
                    }
                    player._isWebFullScreen_ = !player._isWebFullScreen_;
                    return true
                }
            },
            'include': ['||douyu.com^']
        }

    ];

    ;
    const uCheck = (function () {
        function seperator(x) {
            return x == '' || /^[^\w\d\-\.\%\_]$/.test(x)
        }

        return function uCheck(rule, url) {

            let url_s01 = url.split('\:\/\/');
            let protocol = url_s01[0]
            let url_s01r = url.substring(protocol.length + 3);
            let rule_m2 = null;

            if (rule_m2 = /^\|(.+)\|$/.exec(rule)) {
                return url == rule_m2[1];
            }

            let rule_m1 = rule.match(/^(\|\|)?([^\^]+)(\^)?$/)
            let w = '';
            let o = {}
            if (!rule_m1) return null;
            //console.log(110, rule_m1)
            if (rule_m1[3]) o.last = true
            if (rule_m1[1]) o.domain = true
            if (rule_m1[2]) w = rule_m1[2];
            let wq = w.replace(/\//g, '\\/').replace(/\./g, '\\.').replace(/\|/g, '\\|').replace(/\*/g, '.*');
            let rq = new RegExp('^(.*)(' + wq + ")(.?)", 'i')
            let exec1 = rq.exec(url_s01r);
            //console.log(120, rq, url_s01r, exec1)

            if (!exec1) return false;
            if (o.domain) {
                exec1[1] = exec1[1] || '';
                if (exec1[1]) {
                    if (exec1[1].indexOf(':\/\/') >= 0) return false
                    if (!/\./.test(exec1[1].substr(-1))) return false

                }
            }
            if (o.last) {
                if (!seperator(exec1[3])) return false;
            }
            return true;
        }

    })();

    const TCC = {
        /**
         * 任務配置中心 Task Control Center
         * 用於配置所有無法進行通用處理的任務,如不同網站的FULLSCREEN方式不一樣,必須調用網站本身的FULLSCREEN邏輯,才能確保字幕、彈幕等正常工作
         * */

        /**
         * 獲取任務配置,只能獲取到當前域名下的任務配置信息
         * @param taskName {string} -可選 指定具體任務,默認返回所有類型的任務配置
         */
        getTaskConfig: function () {
            let t = this

            if (t._getTaskConfig) return t._getTaskConfig;

            //only just once

            let matchAllDomain = false;
            /**
             * 格式化配置任務
             * @param matchAllDomain { boolean } -可選 默認只格式當前域名或host下的配置任務,傳入true則將所有域名下的任務配置都進行格式化
             */

            let formatTCC = (function () {

                let result = []
                let url = location + '';
                TCC_items.forEach((item) => {
                    if (!tool.isObj(item)) return;
                    if (item.exclude) {
                        if (item.exclude.some(rule => uCheck(rule, url))) {
                            item.isExcluded = true;
                            return;
                        }
                    }

                    if (item.include) {
                        if (item.include.some(rule => uCheck(rule, url))) {
                            item.isIncluded = true;
                        }

                    }

                    if (item.isIncluded) {
                        result.push(item)
                        item.isMatch = true;
                    }

                })
                return result
            })();

            consoleLog('formatTCC', formatTCC)

            let taskConf = formatTCC[0]
            if (!taskConf) return (t._getTaskConfig = {});
            let isMatch = taskConf.isMatch; /* 判斷所提供的配置任務是否適用於當前URL */

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

            if (!taskName) return false
            let taskConf = this.getTaskConfig()
            if (!tool.isObj(taskConf)) return false
            let task = taskConf[taskName]

            if (!task) return false
            consoleLog('h5player-dotask', taskName)
            let clickDOM = null;
            if (taskName === 'shortcuts') {
                if (tool.isObj(task) && task.callback instanceof Function) {

                    return task.callback(h5Player, taskConf, data)
                }
            } else if (task instanceof Function) {
                task(h5Player, taskConf, data)
                try {
                    return task(h5Player, taskConf, data)
                } catch (e) {
                    console.error('TCC自定义函数任务执行失败:', h5Player, taskConf, data);
                    return false
                }
            } else {
                let wrapDom = h5Player.getPlayerWrapDom()
                /* 觸發選擇器上的點擊事件 */
                if (wrapDom && wrapDom.querySelector(task)) {
                    // 在video的父元素裡查找,是為了盡可能相容多實例下的邏輯
                    clickDOM = wrapDom.querySelector(task)
                } else if (document.querySelector(task)) {
                    clickDOM = document.querySelector(task)
                }
                if (clickDOM) {
                    requestAnimationFrame(function () {
                        //prevent keydown and click same time
                        clickDOM.click()
                    });
                    return true
                }
            }
            return false
        }
    }

    class VideoListener {
        constructor(shadowRoot) {
            this.usable = false;
            this.rootElement = shadowRoot || window.document;
            if (this.rootElement) {
                if (this.rootElement.__videoListenerEnabled__) {

                } else {
                    this.rootElement.__videoListenerEnabled__ = true;
                    this.usable = true;
                }

            }
            this.observer = null;
            this._check = () => this.checkEach();

        }
        listen(fn) {
            if (!this.usable) return;
            this.init_observer();
            this.fn = fn; // single function
            this.checkOnce();
            this.checkEach();
        }
        init_observer() {
            if (this.observer) return;
            this.observer = new window.__MutationObserver(this.mutationObserverCallback);
            this.observer.observe(this.rootElement, {
                childList: true,
                subtree: true
            })
            this.observer.videoListener = this;
        }
        mutationObserverCallback(mutations, observer) {

            let videoListener = observer.videoListener;

            let requireChecking = false;

            if (mutations && 'length' in mutations) {
                requireChecking = Array.prototype.some.call(mutations, mutation => {
                    let addedNodes = mutation.addedNodes;
                    if (addedNodes && addedNodes.length) {
                        Array.prototype.forEach.call(addedNodes, addedNode => {
                            if (addedNode.shadowRoot) consoleLog('tt', addedNode.shadowRoot)
                        })
                        return Array.prototype.some.call(addedNodes, addedNode => {

                            if (addedNode.nodeName == 'VIDEO' || addedNode.childElementCount > 0) {
                                return true;
                            }
                            return false;

                        })
                    }

                    return false;

                })

            }

            if (requireChecking) {
                if (videoListener.cid) cancelAnimationFrame(videoListener.cid);
                videoListener.cid = requestAnimationFrame(videoListener._check);
            }

        }

        async checkOnce() {

            var treeWalker = document.createTreeWalker(
                document.documentElement,
                NodeFilter.SHOW_ELEMENT, {
                    acceptNode: function (node) {
                        return node.shadowRoot ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
                    }
                },
                false
            );
            var nodeList = [];

            while (treeWalker.nextNode()) nodeList.push(treeWalker.currentNode);

            nodeList.forEach(node => this._checkRoot(node.shadowRoot))

        }

        async checkEach() {

            this._checkRoot(this.rootElement)

        }
        _checkRoot(shadowRoot) {


            let videos = shadowRoot.querySelectorAll('VIDEO'); //shadowRoot don't have getElementsByTagName
            for (let video of videos) {

                if (!video._isMutationReady_) {
                    video._isMutationReady_ = true
                    video.setAttribute('h5p_observed', '1');
                    this.fn(video)

                }
            }

        }
    }
    window.__MutationObserver = window.MutationObserver || window.WebKitMutationObserver || null

    const _keyMap = function (arr, obj) {

        let res = {}
        arr.forEach((key) => {
            res[key] = key.charCodeAt(0)
        })
        Object.assign(res, obj)
        return res;

    }

    Object.assign(tool, {
        checkFullScreen: function checkFullScreen(doc) {
            if (typeof doc.fullScreen == 'boolean') return doc.fullScreen;
            if (typeof doc.webkitIsFullScreen == 'boolean') return doc.webkitIsFullScreen;
            if (typeof doc.mozFullScreen == 'boolean') return doc.mozFullScreen;
            return null;
        },

        formatCT: function (u) {

            let w = Math.round(u, 0)
            let a = w % 60
            w = (w - a) / 60
            let b = w % 60
            w = (w - b) / 60
            let str = ("0" + b).substr(-2) + ":" + ("0" + a).substr(-2);
            if (w) str = w + ":" + str

            return str

        },
        quickSort: (
            unsortedArray,
            comparator = (a, b) => a < b ? -1 : a > b ? 1 : 0
        ) => {

            //https://medium.com/@Charles_Stover/implementing-quicksort-in-javascript-8044a8e2bf39

            // Create a sortable array to return.
            const sortedArray = [...unsortedArray];

            // Recursively sort sub-arrays.
            const recursiveSort = (start, end) => {

                // If this sub-array is empty, it's sorted.
                if (end - start < 1) {
                    return;
                }

                const pivotValue = sortedArray[end];
                let splitIndex = start;
                for (let i = start; i < end; i++) {
                    const sort = comparator(sortedArray[i], pivotValue);

                    // This value is less than the pivot value.
                    if (sort === -1) {

                        // If the element just to the right of the split index,
                        //   isn't this element, swap them.
                        if (splitIndex !== i) {
                            const temp = sortedArray[splitIndex];
                            sortedArray[splitIndex] = sortedArray[i];
                            sortedArray[i] = temp;
                        }

                        // Move the split index to the right by one,
                        //   denoting an increase in the less-than sub-array size.
                        splitIndex++;
                    }

                    // Leave values that are greater than or equal to
                    //   the pivot value where they are.
                }

                // Move the pivot value to between the split.
                sortedArray[end] = sortedArray[splitIndex];
                sortedArray[splitIndex] = pivotValue;

                // Recursively sort the less-than and greater-than arrays.
                recursiveSort(start, splitIndex - 1);
                recursiveSort(splitIndex + 1, end);
            };

            // Sort the entire array.
            recursiveSort(0, unsortedArray.length - 1);
            return sortedArray;
        },

        getType: function (obj) {

            /**
             * 準確地獲取對象的具體類型
             * @param obj { all } -必選 要判斷的對象
             * @returns {*} 返回判斷的具體類型
             */
            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
        },

        isObj: function isObj(obj) {
            return tool.getType(obj) === 'object'
        }
    });

    Object.assign(UA, {
        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'
            }
        }
    });

    Object.assign(UA, {
        fakeConfig: {
            // 'tv.cctv.com': userAgentMap.iPhone.chrome,
            // 'v.qq.com': userAgentMap.iPad.chrome,
            'open.163.com': UA.userAgentMap.iPhone.chrome,
            'm.open.163.com': UA.userAgentMap.iPhone.chrome
        }

    });

    function debugMsg() {
        console.info('h5player debug message :', ...arguments);
    }

    const _shadowDomList_ = [];

    h5Player = {
        /* 提示文本的字號 */
        fontSize: 16,
        enable: true,
        playerInstance: null,
        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 = Array.prototype.slice.call(document.querySelectorAll('video'))
            // 被封裝在 shadow dom 裡面的video
            if (_shadowDomList_) {
                _shadowDomList_.forEach(shadowRoot => {
                    list = [...list, ...shadowRoot.querySelectorAll('video')]
                })
            }
            //console.log(list)
            return list
        },
        getPlayerWrapDom: function () {
            let t = this
            let player = t.player()
            if (!player || !player.getBoundingClientRect) return
            let wrapDom = null
            let layoutBox = player.getBoundingClientRect()
            if (layoutBox.width && layoutBox.height) {
                domTool.eachParentNode(player, function (parent) {
                    if (parent === document || !parent.getBoundingClientRect) return
                    let parentBox = parent.getBoundingClientRect()
                    if (parentBox.width === layoutBox.width && parentBox.height === layoutBox.height) {
                        wrapDom = parent
                    }
                })
            }
            return wrapDom
        },
        makeFocus: function (player, evt) {
            setTimeout(function () {
                let rpid = player.getAttribute('_h5ppid');
                let actionBox = getRoot(player).querySelector(`[_h5p_actionbox_="${rpid}"]`);

                //console.log('p',rpid, player,actionBox,document.activeElement)
                if (actionBox && actionBox != document.activeElement && !actionBox.contains(document.activeElement)) {
                    consoleLog('make focus on', actionBox)
                    actionBox.focus();

                }

            }, 300)
        },

        _actionBoxSet: function (player) {

            if (!player) return null;
            let vpid = player.getAttribute('_h5ppid');
            if (!vpid) return null;

            let {
                layoutBox,
                wPlayer
            } = h5Player.getLayoutBox(player)

            if (!layoutBox) return null;

            let walkRes = domTool._isActionBox_1(player, layoutBox);
            let parentCount = walkRes.length;
            let actionBox = null;
            if (parentCount - 1 >= 0 && domTool._isActionBox(player, walkRes, parentCount - 1)) {
                actionBox = walkRes[parentCount - 1].elm;
            } else if (parentCount - 2 >= 0 && domTool._isActionBox(player, walkRes, parentCount - 2)) {
                actionBox = walkRes[parentCount - 2].elm;
            } else {
                actionBox = player;
            }
            if (actionBox && actionBox.getAttribute('_h5p_actionbox_') != vpid) {
                consoleLog('D-1', actionBox);
                for (let elm_of_vpid of getRoot(player).querySelectorAll(`[_h5p_actionbox_="${vpid}"]`)) {
                    //change of parentNode
                    elm_of_vpid.removeAttribute('_h5p_actionbox_');
                    _action_eventsBind(elm_of_vpid, 'removeEventListener');
                }
                actionBox.setAttribute('_h5p_actionbox_', vpid);
                if (!actionBox.hasAttribute('tabindex')) actionBox.setAttribute('tabindex', '-1');
                _action_eventsBind(actionBox, 'addEventListener');

                let elm = player;
                while (elm && elm != actionBox) {

                    if (elm.hasAttribute('tabindex')) _action_eventsBind(elm, 'addEventListener');

                    elm = elm.parentNode
                }

            }
            return actionBox;
        },

        videoSrcFound: function (player) {

            // src loaded

            let t = h5Player;
            if (!player) return;
            let vpid = player.getAttribute('_h5ppid') || null;
            if (!vpid || !player.currentSrc) return;

            player._isThisPausedBefore_ = false;

            player.removeAttribute('_h5p_uid_encrypted');

            if (player._record_continuous) player._record_continuous._lastSave = -999; //first time must save

            let uid_A = location.pathname.replace(/[^\d+]/g, '') + '.' + location.search.replace(/[^\d+]/g, '');
            let _uid = location.hostname.replace('www.', '').toLowerCase() + '!' + location.pathname.toLowerCase() + 'A' + uid_A + 'W' + player.videoWidth + 'H' + player.videoHeight + 'L' + (player.duration << 0);

            digestMessage(_uid).then(function (_uid_encrypted) {

                let d = +new Date;

                let recordedTime = null;

                ;
                (function () {
                    //read the last record only;

                    let k1 = '_h5_player_play_progress_';
                    let k1n = '_play_progress_';
                    let k2 = _uid_encrypted;
                    let k3 = k1 + k2;
                    let k3n = k1n + k2;
                    let m2 = Object.keys(localStorage).filter(key => key.substr(0, k3.length) == k3); //all progress records for this video
                    let m2v = m2.map(keyName => +(keyName.split('+')[1] || '0'))
                    let m2vMax = Math.max(0, ...m2v)
                    if (!m2vMax) recordedTime = null;
                    else {
                        let _json_recordedTime = null;
                        _json_recordedTime = Store.read(k3n + '+' + m2vMax);
                        if (!_json_recordedTime) _json_recordedTime = {};
                        else _json_recordedTime = jsonParse(_json_recordedTime);
                        if (typeof _json_recordedTime == 'object') recordedTime = _json_recordedTime;
                        else recordedTime = null;
                        recordedTime = typeof recordedTime == 'object' ? recordedTime.t : recordedTime;
                        if (typeof recordedTime == 'number' && (+recordedTime >= 0 || +recordedTime <= 0)) {} else if (typeof recordedTime == 'string' && recordedTime.length > 0 && (+recordedTime >= 0 || +recordedTime <= 0)) {
                            recordedTime = +recordedTime
                        } else {
                            recordedTime = null
                        }
                    }
                    if (recordedTime !== null) {
                        player._h5player_lastrecord_ = recordedTime;
                    } else {
                        player._h5player_lastrecord_ = null;
                    }
                    if (player._h5player_lastrecord_ > 5) {
                        consoleLog('last record playing', player._h5player_lastrecord_);
                        setTimeout(function () {
                            let tmp_player = h5Player.playerInstance;
                            h5Player.playerInstance = player;
                            h5Player.tips(`Press Shift-R to restore Last Playback: ${tool.formatCT(player._h5player_lastrecord_)}`, 5000, 4000)
                            h5Player.playerInstance = tmp_player
                        }, 1000)
                    }

                })();
                // delay the recording by 5.4s => prevent ads or mis operation
                setTimeout(function () {

                    let k1 = '_h5_player_play_progress_';
                    let k1n = '_play_progress_';
                    let k2 = _uid_encrypted;
                    let k3 = k1 + k2;
                    let k3n = k1n + k2;

                    //re-read all the localStorage keys
                    let m1 = Object.keys(localStorage).filter(key => key.substr(0, k1.length) == k1); //all progress records in this site
                    let p = m1.length + 1;

                    let m2 = m1.filter(key => key.substr(0, k3.length) == k3) //all progress records for this video
                    m2.forEach(key => localStorage.removeItem(key), p--); //remove previous record for the current video

                    if (recordedTime !== null) {
                        Store.save(k3n + '+' + d, jsonStringify({
                            't': recordedTime
                        })) //prevent loss of last record
                    }

                    const _record_max_ = 48;
                    const _record_keep_ = 26;

                    if (p > _record_max_) {
                        //exisiting 48 records for one site;
                        //keep only 26 records

                        const comparator = (a, b) => (a.t < b.t ? -1 : a.t > b.t ? 1 : 0);
                        let mapM = m1.map(keyName => ({
                            keyName,
                            t: +(keyName.split('+')[1] || '0')
                        }));
                        let mapN = tool.quickSort(mapM, comparator).slice(0, -_record_keep_);

                        /* 刪除最早添加的記錄項 */
                        mapN.forEach(function (item) {
                            localStorage.removeItem(item.keyName)
                        })

                        consoleLog(`stored progress: reduced to ${_record_keep_}`)
                    }

                    player.setAttribute('_h5p_uid_encrypted', _uid_encrypted + '+' + d);

                    //try to start recording
                    if (player._record_continuous) player._record_continuous.playingWithRecording();

                }, 5400);

            })

        },
        bindKeyEvents: function (rootNode) {
            let t = h5Player;
            if (!rootNode._bindKeyEvents_) {
                rootNode._bindKeyEvents_ = true;
                rootNode.removeEventListener('keydown', t.keydownEvent, true)
                rootNode.addEventListener('keydown', t.keydownEvent, true)
                document._debug_rootNode_ = rootNode;
            }
        },
        fireGlobalInit: function () {

            let t = this

            /* 綁定鍵盤事件 */
            if (t.init_count != 1) return

            if (!t.srcList) t.srcList = {};

            h5Player.isLostFocus = null;
            consoleLog('keydown bind')
            h5Player.bindKeyEvents(document);

            try {
                //iframe may not be able to control top window
                //error; just ignore with asycn
                let topDoc = window.top && window.top.document ? window.top.document : null
                if (topDoc) {
                    topDoc.addEventListener('focusout', handle.doc_focusout, true)
                    topDoc.addEventListener('focusin', handle.doc_focusin, true)
                }

            } catch (e) {}

            Store.clearInvalid(_sVersion_)

            let host = window.location.host
            if (UA.fakeConfig[host]) {
                t.setFakeUA(UA.fakeConfig[host])
            }

        },
        onVideoTriggering: function () {

            // initialize a single video player - h5Player.playerInstance

            /**
             * 初始化播放器實例
             */
            let t = this
            let player = t.playerInstance
            if (!player) return

            let vpid = player.getAttribute('_h5ppid');

            //            console.log('view',vpid)

            if (!player.getAttribute('_h5ppid')) return;

            let firstTime = !!t.initTips()
            if (firstTime) {
                // first time to trigger this player

                //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');
                //player.style['image-rendering'] = '-webkit-optimize-contrast';
                player.style['image-rendering'] = 'crisp-edges';

                t.filter.reset()
                t.playbackRate = t.getPlaybackRate()

                /* 進行自定義初始化操作 */
                let taskConf = TCC.getTaskConfig()
                if (taskConf.init) {
                    TCC.doTask('init', player)
                }
            }

        },
        getPlaybackRate: function () {
            let t = this
            let playbackRate = Store.read('_playback_rate_') || t.playbackRate
            return Number(Number(playbackRate).toFixed(1))
        },
        getLayoutBox: function (player, skipPlayer) {
            //without checkActiveBox, just a DOM for you to append tipsDom

            var layoutBox = null,
                wPlayer = null

            if (!player || !player.offsetHeight || !player.offsetWidth) {
                return {
                    layoutBox,
                    wPlayer
                };
            }

            if (!player.parentNode) {
                return {
                    layoutBox,
                    wPlayer
                };
            }

            function search_nodes() {

                wPlayer = player; // NOT NULL
                layoutBox = wPlayer.parentNode; // NOT NULL

                while (layoutBox.parentNode && layoutBox.nodeType == 1 && layoutBox.offsetHeight == 0) {
                    wPlayer = layoutBox; // NOT NULL
                    layoutBox = layoutBox.parentNode; // NOT NULL
                }
                while (layoutBox.parentNode && layoutBox.nodeType == 1 && layoutBox.offsetHeight < player.offsetHeight) {
                    wPlayer = layoutBox; // NOT NULL
                    layoutBox = layoutBox.parentNode; // NOT NULL
                }

            }

            search_nodes();

            if (layoutBox.nodeType == 11) {


                //shadowRoot without html and body
                let shadowChild, shadowElm_container, shadowElm_head, shadowElm_html;
                let rootNode = getRoot(player);
                if (rootNode.querySelectorAll('html,body').length < 2) {

                    shadowElm_container = player.ownerDocument.createElement('BODY')
                    rootNode.insertBefore(shadowElm_container, rootNode.firstChild)

                    while (shadowChild = shadowElm_container.nextSibling) shadowElm_container.appendChild(shadowChild);

                    shadowElm_head = rootNode.insertBefore(player.ownerDocument.createElement('HEAD'), shadowElm_container)

                    shadowElm_html = rootNode.insertBefore(player.ownerDocument.createElement('HTML'), shadowElm_head)

                    shadowElm_container.setAttribute('style', 'padding:0;margin:0;border:0;    box-sizing: border-box;')
                    shadowElm_html.setAttribute('style', 'padding:0;margin:0;border:0;    box-sizing: border-box;')

                    shadowElm_html.appendChild(shadowElm_head)
                    shadowElm_html.appendChild(shadowElm_container)

                }

                search_nodes();

            }

            //condition:
            //!layoutBox.parentNode || layoutBox.nodeType != 1 || layoutBox.offsetHeight > player.offsetHeight

            // layoutBox is a node contains <video> and offsetHeight>=video.offsetHeight
            // wPlayer is a HTML Element (nodeType==1)
            // you can insert the DOM element into the layoutBox

            if (layoutBox.nodeType !== 1) {
                //unexpected
                layoutBox = null;
                wPlayer = null;
            }

            return {
                layoutBox,
                wPlayer
            };

        },
        toParentContains: function (layoutBox, selector) {

            let r = layoutBox
            while (r && r.nodeType == 1 && !r.querySelector(selector)) r = r.parentNode;
            r = r || layoutBox;
            if (r && r.nodeType == 1) {
                return r;
            }

            return null;
        },
        getPlayerCTBox: function (elm1, elm2) {

            let box1 = elm1;
            let box2 = elm2;

            while (box1 && box2) {
                if (box1.contains(box2) || box2.contains(box1)) {
                    break;
                }
                box1 = box1.parentNode;
                box2 = box2.parentNode;
            }

            let layoutBox = null;

            box1 = (box1 && box1.contains(elm2)) ? box1 : null;
            box2 = (box2 && box2.contains(elm1)) ? box2 : null;

            if (box1 && box2) layoutBox = box1.contains(box2) ? box2 : box1;
            else layoutBox = box1 || box2 || null;

            return layoutBox

        },
        change_layoutBox: function (tipsDom) {
            let t = h5Player;
            let player = t.player()
            let {
                layoutBox,
                wPlayer
            } = t.getLayoutBox(player)

            if (wPlayer && layoutBox && wPlayer.parentNode == layoutBox) {} else {
                //unexpected
                return;
            }
            //console.log(layoutBox,wPlayer)
            //console.log('cp',layoutBox)
            if (layoutBox && layoutBox.nodeType == 1) {
                if (!tipsDom.parentNode || tipsDom.parentNode !== layoutBox) {

                    consoleLog('changed_layoutBox')
                    layoutBox.insertBefore(tipsDom, wPlayer);

                    if (tipsDom._vpr && tipsDom._vpr_obj && tipsDom._vpr_pn) {
                        let rm = ResizeODM.find(tipsDom._vpr_pn)
                        if (rm) {
                            rm.unlisten(tipsDom._vpr)
                            tipsDom._vpr_pn = tipsDom.parentNode
                            rm.relativeParent(layoutBox, wPlayer)
                            rm.listen(tipsDom._vpr)
                            rm.odm.id = tipsDom._vpr_obj.id
                            tipsDom._vpr_obj = rm.odm;
                        } else {
                            tipsDom._vpr_pn = tipsDom.parentNode
                        }

                    }

                }
            }
        },
        callFullScreenBtn0: function () {
            return this.setWebFullScreen();
        },
        getPlayerTips: function () {

            //return this.callFullScreenBtn7();
            let t = this;
            let player = t.player()

            let tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName);
            return getRoot(player).querySelector('#' + tcn)
        },
        setWebFullScreen: function () {

            //return this.callFullScreenBtn7();
            let t = this;
            let player = t.player()
            let tipsDom = t.getPlayerTips();

            let {
                layoutBox,
                wPlayer
            } = t.getLayoutBox(player);

            let rpid = player.getAttribute('_h5ppid') || "NULL";
            let gPlayer = null;
            if (layoutBox && layoutBox.parentNode) {
                let actionBox = layoutBox.parentNode.querySelector(`[_h5p_actionbox_="${rpid}"]`); //the box can be layoutBox

                gPlayer = actionBox
                //gPlayer = t.getPlayerCTBox(actionBox, tipsDom);
            }

            if (gPlayer) {
                consoleLog('gPlayer', gPlayer)

                let chFull = tool.checkFullScreen(gPlayer.ownerDocument);

                if (chFull === true) {
                    consoleLog('chFull', 'true')
                    player.ownerDocument.exitFullscreen();
                } else {
                    consoleLog('chFull', 'false')
                    consoleLog('DOM fullscreen')
                    //          consoleLogF(gPlayer)
                    try {
                        gPlayer.requestFullscreen() //known bugs : TypeError: fullscreen error
                    } catch (e) {
                        consoleLogF('fullscreen error', e)
                    }
                }
            } else {
                consoleLog('the container for DOM fullscreen cannot be found')
            }

        },
        callFullScreenBtn: function () {
            //return this.setWebFullScreen();
            //return this.callFullScreenBtn7();
            //return this.callFullScreenBtn8();
            let t = this;
            let player = t.player()
            if (!player || !player.ownerDocument) return this.callFullScreenBtn0();
            let tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName);
            consoleLog('tcn', tcn)
            let tipsDom = getRoot(player).querySelector('#' + tcn)
            if (!tipsDom) return this.callFullScreenBtn0();
            let chFull = tool.checkFullScreen(player.ownerDocument);
            if (chFull === null) return (console.log('chFull', 'null'), this.callFullScreenBtn0());
            if (chFull === true) {
                consoleLog('chFull', 'true')
                player.ownerDocument.exitFullscreen();
                //  let el = [t.player_focus_input]; if (el) {el = el[0].click()}
            } else {
                consoleLog('chFull', 'false')

                const layoutBox = t.getPlayerCTBox(player, tipsDom);

                let pPlayer = player
                let qPlayer = layoutBox
                let clicked = false;
                let gs1;
                let _gs1_tmp = {};
                let _gs1_filter = function (elq) {
                    return _gs1_tmp.elm == elq.elm ? false : _gs1_tmp.elm.contains(elq.elm)
                };
                let _gs1_contains = function (elp) {
                    if (elp.childElementCount === 0) return false;
                    _gs1_tmp.elm = elp.elm;
                    elp.isContained = gs1.filter(_gs1_filter).length > 0;
                }
                // try to find the fullscreen button
                for (let q3 = 0; q3 < 4; q3++) { //max 4 layers
                    if (!qPlayer) break;
                    let fs1 = qPlayer.querySelectorAll('[class*="fullscreen"]')
                    if (fs1.length > 0) {
                        // -- indiv-elm --
                        gs1 = Array.prototype.map.call(fs1, function (elm) {
                            return {
                                elm: elm,
                                isVisible: null,
                                hasClickListeners: null,
                                childElementCount: null,
                                isContained: null
                            }
                        });
                        if (('_listeners' in document)) {
                            gs1.forEach(function (elp) {
                                let elm = elp.elm;
                                elp.hasClickListeners = elm._listeners && elm._listeners.click && elm._listeners.click.funcCount > 0
                            })
                        }
                        if ('childElementCount' in player) {
                            gs1.forEach(function (elp) {
                                let elm = elp.elm;
                                elp.childElementCount = elm.childElementCount;
                            })
                        }
                        if ('offsetParent' in player) {
                            gs1.forEach(function (elp) {
                                let elm = elp.elm;
                                elp.isVisible = !!elm.offsetParent; //works with parent/self display none; not work with visiblity hidden / opacity0
                            })
                        }
                        gs1 = gs1.filter(function (elp) {
                            return elp.hasClickListeners
                        })
                        //console.log('gs1',gs1)
                        // -- inter-elm --
                        if ('contains' in player) {
                            gs1.forEach(_gs1_contains)
                        }
                        let gs2 = gs1.filter(function (elp) {
                            return !elp.isContained && elp.isVisible
                        })
                        consoleLog('fullscreen btn', gs2)
                        //console.log('gs2',gs2)
                        if (gs2.length >= 1) {
                            let gs2_a = gs2.map(elp => elp.elm.className.length)
                            let gs2_b = Math.min.apply(Math, gs2_a)
                            let gs2_c = gs2_a.lastIndexOf(gs2_b)
                            // pick the last btn if there is more than one
                            gs2[gs2_c].elm.click();
                            clicked = true;
                            consoleLog('original fullscreen')
                            break;
                        }
                    }
                    pPlayer = qPlayer
                    qPlayer = qPlayer.parentNode
                }
                if (!clicked) {
                    //cannot find -> default
                    consoleLog('try HTML5 fullscreen')
                    this.setWebFullScreen();
                }
            }
        },
        /* 設置播放速度 */
        setPlaybackRate: function (num, flagTips) {
            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.1) {
                    num = 0.1
                }
                num = Number(num.toFixed(1))
                curPlaybackRate = num
            } else {
                curPlaybackRate = t.getPlaybackRate()
            }
            /* 記錄播放速度的信息 */

            let changed = curPlaybackRate !== player.playbackRate;

            if (curPlaybackRate !== player.playbackRate) {

                Store.save('_playback_rate_', curPlaybackRate + '')
                t.playbackRate = curPlaybackRate
                player.playbackRate = curPlaybackRate
                /* 本身處於1被播放速度的時候不再提示 */
                //if (!num && curPlaybackRate === 1) return;

            }

            flagTips = (flagTips < 0) ? false : (flagTips > 0) ? true : changed;
            if (flagTips) t.tips('Playback speed: ' + player.playbackRate + 'x')
        },
        tuneCurrentTime: function (amount, flagTips) {
            let _amount = +(+amount).toFixed(1);
            let t = this;
            let player = t.player();
            if (_amount >= 0 || _amount < 0) {} else {
                return;
            }
            let taskConf = TCC.getTaskConfig();
            if (taskConf.currentTime) {
                TCC.doTask('currentTime');
                return
            }
            if (_amount > 0) {
                if (taskConf.addCurrentTime) {
                    TCC.doTask('addCurrentTime')
                    return;
                }
            } else {
                if (taskConf.subtractCurrentTime) {
                    TCC.doTask('subtractCurrentTime')
                    return;
                }
            }

            let newCurrentTime = player.currentTime + _amount;
            if (newCurrentTime < 0) newCurrentTime = 0;
            if (newCurrentTime > player.duration) newCurrentTime = player.duration;

            let changed = newCurrentTime != player.currentTime && newCurrentTime >= 0 && newCurrentTime <= player.duration;

            if (changed) {
                //player.currentTime = newCurrentTime;
                player.pause();
                let t_ch=(Math.random()/5+.75);
                player.currentTime = newCurrentTime*t_ch+player.currentTime*(1.0-t_ch);
                setTimeout(()=>{
                    player.play();
                    player.currentTime = newCurrentTime;
                },33);
                flagTips = (flagTips < 0) ? false : (flagTips > 0) ? true : changed;
                t.tips(false);
                if (flagTips) {
                    if (_amount > 0) t.tips(_amount + ' Sec. Forward', undefined, 3000);
                    else t.tips(-_amount + ' Sec. Backward', undefined, 3000)
                }
            }

        },
        tuneVolume: function (amount) {
            let _amount = +(+amount).toFixed(2);

            let t = this
            let player = t.player()

            let newVol = player.volume + _amount;
            if (newVol < 0) newVol = 0;
            if (newVol > 1) newVol = 1;
            let chVol = player.volume !== newVol && newVol >= 0 && newVol <= 1;

            if (chVol) {

                if (_amount > 0) {
                    if (player.volume < 1) {
                        player.volume = newVol // positive
                    }
                } else {
                    if (player.volume > 0) {
                        player.volume = newVol // negative
                    }
                }
                t.tips(false);
                t.tips('Volume: ' + dround(player.volume * 100) + '%', undefined)
            }
        },
        setFakeUA(ua) {
            ua = ua || UA.userAgentMap.iPhone.safari
            /* 記錄設定的ua信息 */
            Store.save('_user_agent_', ua)
            try {
                Object.defineProperty(navigator, 'userAgent', {
                    value: ua,
                    writable: false,
                    configurable: false,
                    enumerable: true
                });
            } catch (e) {}
        },
        /* ua偽裝切換開關 */
        switchFakeUA(ua) {
            let customUA = Store.read('_user_agent_')
            if (customUA) {
                Store.remove('_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()
                    if(player._isThisPausedBefore_){
                        t.tips(false);
                        t.tips('Playback resumed', undefined, 2500)
                    }
                }
            } else {
                if (taskConf.pause) {
                    TCC.doTask('pause')
                } else {
                    player.pause()
                    t.tips(false);
                    t.tips('Playback paused', undefined, 2500)
                }
            }
        },
        tipsClassName: 'html_player_enhance_tips',
        tips: function (str, duration, order) {
            let t = h5Player
            let player = t.player()
            if (!player) {
                consoleLog('h5Player Tips:', str)
                return true
            }
            let parentNode = player.parentNode

            if (!player.getAttribute('_h5player_tips')) {
                t.initTips();
            }

            let tipsSelector = '#' + (player.getAttribute('_h5player_tips') || t.tipsClassName) //if this attribute still doesnt exist, set it to the base cls name
            let tipsDom = getRoot(player).querySelector(tipsSelector)
            if (!tipsDom) {
                consoleLog('init h5player tips dom error...')
                return false
            }
            t.change_layoutBox(tipsDom);
            let style = tipsDom.style

            if (str === false) {
                tipsDom.innerText = '';
                tipsDom.setAttribute('_potTips_', '0')
            } else {
                order = order || 1000
                tipsDom.tipsOrder = tipsDom.tipsOrder || 0;

                let shallDisplay = true
                if (order < tipsDom.tipsOrder && getComputedStyle(tipsDom).opacity > 0) shallDisplay = false

                if (shallDisplay) {

                    tipsDom._layoutBoxObj = h5Player.getLayoutBox(player)
                    tipsDom._env_changed = 0;

                    if (!_endlessloop) {
                        _endlessloop = new EndlessLoop();
                    }
                    tipsDom._layoutBoxObjSize = null;
                    if (!h5Player.__updateTips) {

                        h5Player.__updateTips = _endlessloop.append(function (opts) {

                            let tipsDoms = h5Player._cached_tipsDoms;

                            if (!tipsDoms || !tipsDoms.length) return;
                            tipsDoms.forEach((tipsDom) => {

                                if (!tipsDom._layoutBoxObj) return;
                                let {
                                    layoutBox,
                                    wPlayer
                                } = tipsDom._layoutBoxObj
                                if (layoutBox == null || wPlayer == null) return;
                                let p = [~~layoutBox.offsetWidth, ~~layoutBox.offsetHeight, ~~wPlayer.offsetWidth, ~~wPlayer.offsetHeight];

                                if (tipsDom._layoutBoxObjSize) {
                                    let changed = false;
                                    for (let i = 0; i < 4; i++) {
                                        if (tipsDom._layoutBoxObjSize[i] == p[i]) {

                                        } else {
                                            changed = true;
                                            break;
                                        }
                                    }
                                    if (!changed) {
                                        return;
                                    }
                                    tipsDom._env_changed = (tipsDom._env_changed || 0) + 1;
                                    consoleLog('tipsDom Env changed', tipsDom.id)
                                }

                                tipsDom._layoutBoxObjSize = p;

                                if (!tipsDom.id) return;
                                let player = layoutBox.querySelector(`[_h5player_tips="${tipsDom.id}"]`)
                                if (!player) return;

                                h5Player.fixNonBoxingVideoTipsPosition(tipsDom, player)

                            })

                        })

                        h5Player.__updateTips.loopingStart();
                    }

                    if (duration === undefined) duration = 2000
                    tipsDom.innerText = str

                    tipsDom.setAttribute('_potTips_', '2')

                    t.fixNonBoxingVideoTipsPosition(tipsDom, player);

                    if (duration > 0) {

                        requestAnimationFrame(function () {

                            tipsDom.setAttribute('_potTips_', '1')

                        })

                    } else {
                        order = -1;
                    }

                    tipsDom.tipsOrder = order

                }

            }

            h5Player._cached_tipsDoms = document.querySelectorAll('[_potTips_="1"],[_potTips_="2"]'); //48673
        },
        initTips: function () {
            /* 設置提示DOM的樣式 */
            let t = this
            let player = t.player()
            let shadowRoot = getRoot(player);
            let doc = player.ownerDocument;
            //console.log((document.documentElement.qq=player),shadowRoot,'xax')
            let parentNode = player.parentNode
            let tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName + '_' + (+new Date));
            player.setAttribute('_h5player_tips', tcn)
            if (shadowRoot.querySelector('#' + tcn)) return false;

            if (!shadowRoot.__notFirstTips__) {
                shadowRoot.__notFirstTips__ = true;

                domTool.addStyle(`
[_potTips_="1"]{
animation: 2s linear 0s normal forwards 1 delayHide;
}
[_potTips_="0"]{
opacity:0; transform:translate(-9999px);
}
[_potTips_="2"]{
opacity:.95; transform: translate(0,0);
}

@keyframes delayHide{
0%, 99% { opacity:0.95; transform: translate(0,0); }
100% { opacity:0; transform:translate(-9999px); }
}
` + `
[_potTips_]{
font-weight: bold !important;
position: absolute !important;
z-index: 999 !important;
font-size: ${t.fontSize || 16}px !important;
padding: 0px !important;
border:none !important;
background: rgba(0,0,0,0) !important;
color:#738CE6 !important;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
top: 50%;
left: 50%;
max-width:500px;max-height:50px;
border-radius:3px;
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;
}
`.replace(/\r\n/g, ''), (shadowRoot.querySelector('head') || shadowRoot.querySelector('html') || document.documentElement))

            }

            let tipsDom = doc.createElement('div')

            var transitionEvent = whichTransitionEvent('animation');

            tipsDom.addEventListener(transitionEvent, function (e) {
                if (this.getAttribute('_potTips_') == '1') {
                    this.setAttribute('_potTips_', '0')
                    h5Player._cached_tipsDoms = shadowRoot.querySelectorAll('[_potTips_="1"],[_potTips_="2"]');
                }
            })

            tipsDom.id = tcn;
            tipsDom.setAttribute('unselectable', 'on');
            tipsDom.setAttribute('_potTips_', '0');
            t.change_layoutBox(tipsDom);

            tipsDom._vpr = function () {

                let resizeDOM = this;
                if (!resizeDOM) return;

                console.log('The container of the video is resized', resizeDOM.odm.id);

                // let odm=resizeDOM.odm
                // if(!odm) return;
                // let tip_id=odm.id.slice(0,-3)
                // console.log('objectLoad 3',this.__resizeTrigger__,`${tip_id}`)
                // var player=document.querySelector(`[_h5player_tips="${tip_id}"]`)
                // if(!player)return;
                // let {layoutBox,wPlayer}=h5Player.getLayoutBox(player,false);

                // console.log('layoutBox', layoutBox);
                // console.log('wPlayer', wPlayer);
                // console.log('odm', odm);
                // console.log('layoutBoxW', layoutBox.offsetWidth,odm.offsetWidth);
                // console.log('layoutBoxH', layoutBox.offsetHeight,odm.offsetHeight);
                // console.log('wPlayerW', wPlayer.offsetWidth,odm.offsetWidth);
                // console.log('wPlayerH', wPlayer.offsetHeight,odm.offsetHeight);

                //player.dispatchEvent(new CustomEvent('videoparentresize',{detail:  {parent:this, player:player}}))

                //let layoutBox=h5Player.getLayoutBox(player)
                //resizeDOM.relativeParent(layoutBox.parentNode,layoutBox)

            }
            tipsDom._vpr_pn = tipsDom.parentNode

            tipsDom._vpr = null
            tipsDom._vpr_pn = null

            if (tipsDom._vpr && tipsDom._vpr_pn) {

                let rm = new ResizeODM();
                let tmp_elm = null;
                let {
                    layoutBox,
                    wPlayer
                } = h5Player.getLayoutBox(player, false);
                if (layoutBox && wPlayer) tmp_elm = layoutBox.parentNode != tipsDom._vpr_pn ? null : layoutBox;

                rm.relativeParent(tipsDom._vpr_pn, tmp_elm)
                rm.listen(tipsDom._vpr)

                if (rm.odm) {
                    rm.odm.id = tcn + 'obj'
                    //console.log(rm.odm,rm.odm.id)
                    tipsDom._vpr_obj = rm.odm;
                }

            }

            return true;
        },

        responsiveSizing: function (container, elm) {

            let gcssP = getComputedStyle(container)

            let gcssE = getComputedStyle(elm)

            //console.log(gcssE.left,gcssP.width)
            let elmBound = {
                left: parseFloat(gcssE.left) / parseFloat(gcssP.width)

                ,
                width: parseFloat(gcssE.width) / parseFloat(gcssP.width)

                ,
                top: parseFloat(gcssE.top) / parseFloat(gcssP.height)

                ,
                height: parseFloat(gcssE.height) / parseFloat(gcssP.height)
            }

            let elm00 = [elmBound.left, elmBound.top];
            let elm01 = [elmBound.left + elmBound.width, elmBound.top];
            let elm10 = [elmBound.left, elmBound.top + elmBound.height];
            let elm11 = [elmBound.left + elmBound.width, elmBound.top + elmBound.height];

            return {
                elm00,
                elm01,
                elm10,
                elm11,
                plw: elmBound.width,
                plh: elmBound.height
            }

        },

        fixNonBoxingVideoTipsPosition: function (tipsDom, player) {

            if (!tipsDom || !player) return;

            let ct = h5Player.getPlayerCTBox(tipsDom, player)

            if (!ct) return;

            //console.log('fixNonBoxingVideoTipsPosition')

            //relative

            let {
                elm00,
                elm01,
                elm10,
                elm11,
                plw,
                plh
            } = h5Player.responsiveSizing(ct, player)

            if (isNaN(elm00[0]) || isNaN(elm00[1])) {

                [tipsDom.style.left, tipsDom.style.top] = [player.style.left, player.style.top];
                //eg auto
            } else {

                let rlm00 = elm00.map(t => (t * 100).toFixed(2) + '%');
                //console.log(rlm00);

                [tipsDom.style.left, tipsDom.style.top] = rlm00;

            }

            // absolute

            let _offset = {
                left: 10,
                top: 15
            };

            let customOffset = {
                left: _offset.left,
                top: _offset.top
            };
            let p = tipsDom.getBoundingClientRect();
            let q = player.getBoundingClientRect();
            let currentPos = [p.left, p.top];

            //new FTC(plw).mul(0.5)
            //new FTC(plh).mul(0.5)

            let targetPos = [q.left + player.offsetWidth * 0 + customOffset.left, q.top + player.offsetHeight * 0 + customOffset.top];

            let mL = +tipsDom.style.marginLeft.replace('px', '') || 0;
            if (isNaN(mL)) mL = 0;
            let mT = +tipsDom.style.marginTop.replace('px', '') || 0;
            if (isNaN(mT)) mT = 0;

            let z1 = -(currentPos[0] - targetPos[0]);
            let z2 = -(currentPos[1] - targetPos[1]);

            if (z1 || z2) {

                //console.log('A01-currentPos',...currentPos);
                //console.log('A02-targetPos',...targetPos);
                let y1 = z1 + mL;
                let y2 = z2 + mT;

                tipsDom.style.marginLeft = y1 + 'px';
                tipsDom.style.marginTop = y2 + 'px';

                // let z=tipsDom.getBoundingClientRect();
                //let zcurrentPos=[(z.left+z.right)/2,(z.bottom+z.top)/2];
                //console.log('A03-currentPos',...zcurrentPos);

            }
        },

        rotate: 0,
        fps: 30,
        /* 濾鏡效果 */
        filter: {
            key: {},
            view_units: {
                'hue-rotate': 'deg',
                'blur': 'px'
            },
            setup: function (options) {
                let view = ''
                for (let view_key in this.key) {
                    let 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)
                }
                view += 'url("#_h5p_unsharpen1")'
                if (options && options.grey) view += ' url("#grey1")'
                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,
        keyMap: _keyMap(["1", "2", "3", "4", "c", "d", "e", "f", "i", "j", "k", "n", "m", "o", "p", "q", "r", "s", "t", "u", "w", "x", "y", "z"], {
            'enter': 13,
            'shift': 16,
            'ctrl': 17,
            'alt': 18,
            'esc': 27,
            'space': 32,
            'LEFT': 37,
            'UP': 38,
            'RIGHT': 39,
            'DOWN': 40,
            'pad1': 97,
            'pad2': 98,
            'pad3': 99,
            'pad4': 100,
            '\\': 220,
        }),
        zoom_keys: ['x', 'c', 'z', 'arrowright', 'arrowleft', 'arrowup', 'arrowdown'],
        /* 播放器事件響應器 */
        playerTrigger: function (player, event) {
            if (!player || !event) return
            let t = h5Player
            let keyCode = lowerKeyCode(event.keyCode)
            if (event.code == "Space" && keyCode > 128) keyCode = 32;
            let keyAsm = (event.shiftKey ? SHIFT : 0) | (event.ctrlKey ? CTRL : 0) | (event.altKey ? ALT : 0);
            //shift + key
            if (keyAsm == SHIFT) {
                let key = event.key.toLowerCase()
                // 網頁FULLSCREEN
                if (key === 'enter') {
                    t.callFullScreenBtn()

                    return TERMINATE

                }
                // 進入或退出畫中畫模式
                else if (key === 'p') {
                    pictureInPicture(player)

                    return TERMINATE
                } else if (key == 'r') {
                    if (player._h5player_lastrecord_ !== null && (player._h5player_lastrecord_ >= 0 || player._h5player_lastrecord_ <= 0)) {
                        t.setPlayProgress(player, player._h5player_lastrecord_)

                        return TERMINATE
                    }

                } else if (key === 'o') {
                    var _debug_h5p_logging_ch = false;
                    try {
                        window.localStorage.setItem('_h5_player_sLogging_', 1 - window.localStorage.getItem('_h5_player_sLogging_'))
                        _debug_h5p_logging_ = +window.localStorage.getItem('_h5_player_sLogging_') > 0;
                        _debug_h5p_logging_ch = true;
                    } catch (e) {

                    }
                    consoleLogF('_debug_h5p_logging_', !!_debug_h5p_logging_, 'changed', _debug_h5p_logging_ch)

                    if (_debug_h5p_logging_ch) {

                        return TERMINATE
                    }
                } else if (key == 't') {
                    if (/^blob/i.test(player.currentSrc)) {
                        alert(`The current video is ${player.currentSrc}\nSorry, it cannot be opened in PotPlayer.`);
                    } else {
                        var confirm_res = confirm(`The current video is ${player.currentSrc}\nDo you want to open it in PotPlayer?`);
                        if (confirm_res) window.open('potplayer://' + player.currentSrc, '_blank');

                    }
                    return TERMINATE
                }
                // 視頻畫面縮放相關事件

                let videoScale = (+player._videoScale) || 0;
                let key_controlled = true;
                switch (key) {
                        // shift+X:視頻縮小 -0.1
                    case 'x':
                        videoScale -= 0.1
                        break
                        // shift+C:視頻放大 +0.1
                    case 'c':
                        videoScale += 0.1
                        break
                        // shift+Z:視頻恢復正常大小
                    case 'z':
                        videoScale = 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
                    default:
                        key_controlled = false;

                }
                if (key_controlled) {

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

                    return TERMINATE
                }

            }
            // 防止其它無關組合鍵衝突
            if (!keyAsm) {
                let kControl = null
                let newPBR, oldPBR;
                switch (keyCode) {
                        // 方向鍵右→:快進3秒
                    case 39:
                        t.tuneCurrentTime(t.skipStep)
                        return TERMINATE
                        break;
                        // 方向鍵左←:後退3秒
                    case 37:
                        t.tuneCurrentTime(-t.skipStep)
                        return TERMINATE
                        break;
                        // 方向鍵上↑:音量升高 1%
                    case 38:
                        if ((player.muted && player.volume === 0) && player._volume > 0) {

                            player.muted = false;
                            player.volume = player._volume;
                        } else if (player.muted && (player.volume > 0 || !player._volume)) {
                            player.muted = false;
                        }
                        t.tuneVolume(0.01)
                        return TERMINATE
                        break;
                        // 方向鍵下↓:音量降低 1%
                    case 40:

                        if ((player.muted && player.volume === 0) && player._volume > 0) {

                            player.muted = false;
                            player.volume = player._volume;
                        } else if (player.muted && (player.volume > 0 || !player._volume)) {
                            player.muted = false;
                        }
                        t.tuneVolume(-0.01)
                        return TERMINATE
                        break;
                        // 空格鍵:暫停/播放
                    case h5Player.keyMap.space:
                        var _p_paused= player.paused;
                        t.switchPlayStatus()
                        if(!_p_paused===player.paused){
                            if(!player.paused && player._isThisPausedBefore_){
                                t.tips(false);
                                t.tips('Playback resumed', undefined, 2500);

                            }else if(player.paused){
                                t.tips(false);
                                t.tips('Playback paused', undefined, 2500);
                            }
                        }
                        return TERMINATE
                        break;
                        // 按鍵X:減速播放 -0.1
                    case h5Player.keyMap.x:
                        if (player.playbackRate > 0) {
                            h5Player.tips(false);
                            t.setPlaybackRate(player.playbackRate - 0.1)
                            return TERMINATE
                        }
                        break;
                        // 按鍵C:加速播放 +0.1
                    case h5Player.keyMap.c:
                        if (player.playbackRate < 16) {
                            h5Player.tips(false);
                            t.setPlaybackRate(player.playbackRate + 0.1)
                            return TERMINATE
                        }

                        break;
                        // 按鍵Z:正常速度播放
                    case h5Player.keyMap.z:
                        h5Player.tips(false);
                        oldPBR = player.playbackRate;
                        if (oldPBR != 1.0) {
                            player._playbackRate_z = oldPBR;
                            newPBR = 1.0;
                        } else if (player._playbackRate_z != 1.0) {
                            newPBR = player._playbackRate_z || 1.0;
                            player._playbackRate_z = 1.0;
                        } else {
                            newPBR = 1.0
                            player._playbackRate_z = 1.0;
                        }
                        t.setPlaybackRate(newPBR, 1)
                        return TERMINATE
                        break;
                        // 按鍵F:下一幀
                    case h5Player.keyMap.f:
                        if (window.location.hostname === 'www.netflix.com') return /* netflix 的F鍵是FULLSCREEN的意思 */
                        h5Player.tips(false);
                        if (!player.paused) player.pause()
                        player.currentTime += Number(1 / t.fps)
                        t.tips('Jump to: Next frame')
                        return TERMINATE
                        break;
                        // 按鍵D:上一幀
                    case h5Player.keyMap.d:
                        h5Player.tips(false);
                        if (!player.paused) player.pause()
                        player.currentTime -= Number(1 / t.fps)
                        t.tips('Jump to: Previous frame')
                        return TERMINATE
                        break;
                        // 按鍵E:亮度增加%
                    case h5Player.keyMap.e:
                        h5Player.tips(false);
                        kControl = 'brightness'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Brightness: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵W:亮度減少%
                    case h5Player.keyMap.w:
                        h5Player.tips(false);
                        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: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵T:對比度增加%
                    case h5Player.keyMap.t:
                        h5Player.tips(false);
                        kControl = 'contrast'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Contrast: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵R:對比度減少%
                    case h5Player.keyMap.r:
                        h5Player.tips(false);
                        kControl = 'contrast'
                        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('Contrast: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵U:飽和度增加%
                    case h5Player.keyMap.u:
                        h5Player.tips(false);
                        kControl = 'saturate'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Saturate: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵Y:飽和度減少%
                    case h5Player.keyMap.y:
                        h5Player.tips(false);
                        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: ' + dround(t.filter.key[kControl] * 100) + '%')
                        return TERMINATE
                        break;
                        // 按鍵O:色相增加 1 度
                    case h5Player.keyMap.o:
                        h5Player.tips(false);
                        kControl = 'hue-rotate'
                        t.filter.key['hue-rotate'] += 1
                        t.filter.setup()
                        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
                        return TERMINATE
                        break;
                        // 按鍵I:色相減少 1 度
                    case h5Player.keyMap.i:
                        h5Player.tips(false);
                        kControl = 'hue-rotate'
                        t.filter.key['hue-rotate'] -= 1
                        t.filter.setup()
                        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
                        return TERMINATE
                        break;
                        // 按鍵K:模糊增加 0.1 px
                    case h5Player.keyMap.k:
                        h5Player.tips(false);
                        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')
                        return TERMINATE
                        break;
                        // 按鍵J:模糊減少 0.1 px
                    case h5Player.keyMap.j:
                        h5Player.tips(false);
                        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')
                        return TERMINATE
                        break;
                        // 按鍵Q:圖像復位
                    case h5Player.keyMap.q:
                        h5Player.tips(false);
                        t.filter.reset()
                        t.tips('Video Filter Reset')
                        return TERMINATE
                        break;
                        // 按鍵S:畫面旋轉 90 度
                    case h5Player.keyMap.s:
                        h5Player.tips(false);
                        t.rotate += 90
                        if (t.rotate % 360 === 0) t.rotate = 0;
                        player.style.transform = 'rotate(' + t.rotate + 'deg)'
                        t.tips('Rotation:' + t.rotate + ' deg')
                        return TERMINATE
                        break;
                        // 按鍵迴車,進入FULLSCREEN
                    case h5Player.keyMap.enter:
                        //t.callFullScreenBtn();
                        break;
                    case h5Player.keyMap.n:
                        pictureInPicture(player);
                        return TERMINATE
                        break;
                    case h5Player.keyMap.m:
                        //console.log('m!', player.volume,player._volume)

                        if (player.volume >= 0) {

                            if (!player.volume || player.muted) {

                                var newVol = player.volume || player._volume || 0.5;
                                if (player.volume !== newVol) {
                                    player.volume = newVol;
                                }
                                player.muted = false;
                                h5Player.tips(false);
                                h5Player.tips('Mute: Off', undefined);

                            } else {

                                player._volume = player.volume;
                                player._volume_p = player.volume;
                                //player.volume = 0;
                                player.muted = true;
                                h5Player.tips(false);
                                h5Player.tips('Mute: On', undefined);

                            }

                        }

                        return TERMINATE
                        break;
                    default:
                        // 按1-4設置播放速度 49-52;97-100
                        if ((keyCode >= 49 && keyCode <= 52) || (keyCode >= 97 && keyCode <= 100)) {
                            h5Player.tips(false);
                            t.setPlaybackRate(Number(event.key), 1)
                            return TERMINATE
                        }
                }

            }
        },
        isRegister: function isRegister(event, registerList, key) {
            let list = registerList
            /* 當前觸發的組合鍵 */
            let combineKey = []
            if (event.ctrlKey) {
                combineKey.push('ctrl')
            }
            if (event.shiftKey) {
                combineKey.push('shift')
            }
            if (event.altKey) {
                combineKey.push('alt')
            }
            combineKey.push(key)
            /* 通過循環判斷當前觸發的組合鍵和已註冊的組合鍵是否完全一致 */
            let hasRegArr = list.filter((shortcut) => {
                let regKey = shortcut.split('+');
                if (combineKey.length === regKey.length) {
                    let allMatch = regKey.every(key => combineKey.includes(key));
                    if (allMatch) {
                        return true;
                    }
                }
                return false;
            })
            return hasRegArr.length == 1
        },

        /* 運行自定義的快捷鍵操作,如果運行了會返回true */
        runCustomShortcuts: function (player, event) {
            if (!player || !event) return

            let t = h5Player;

            let taskConf = TCC.getTaskConfig()
            let confIsCorrect = tool.isObj(taskConf.shortcuts) && Array.isArray(taskConf.shortcuts.register) && taskConf.shortcuts.callback instanceof Function
            /* 判斷當前觸發的快捷鍵是否已被註冊 */

            let key = event.key.toLowerCase()
            if (confIsCorrect && t.isRegister(event, taskConf.shortcuts.register, key)) {
                // 執行自定義快捷鍵操作
                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_moveInPL: function (e) {
            let t = h5Player;
            let player = t.player();
            let rootNode = getRoot(player);

            if (rootNode.pointerLockElement != player) {
                player.removeEventListener('mousemove', h5Player._keydownEvent_moveInPL)
                return;
            }

            var movementX = e.movementX ||
                e.mozMovementX ||
                e.webkitMovementX ||
                0,
                movementY = e.movementY ||
                e.mozMovementY ||
                e.webkitMovementY ||
                0;

            player.__xyOffset.x += movementX
            player.__xyOffset.y += movementY
            var ld = Math.sqrt(screen.width * screen.width + screen.height * screen.height) * .1
            var md = Math.sqrt(player.__xyOffset.x * player.__xyOffset.x + player.__xyOffset.y * player.__xyOffset.y);
            //console.log(player.__xyOffset.x,player.__xyOffset.y)
            if (md > ld) {
                h5Player._keydownEvent_leavePL();
            }

        },

        _keydownEvent_enterPL: function () {
            let t = h5Player;
            let player = t.player();

            if (player) {

                player.__requestPointerLock();
                player.__xyOffset = {
                    x: 0,
                    y: 0
                };

                player.addEventListener('mousemove', h5Player._keydownEvent_moveInPL)
            }
        },

        _keydownEvent_leavePL: function () {
            let t = h5Player;

            let player = t.player();
            if (player) player.removeEventListener('mousemove', h5Player._keydownEvent_moveInPL)
            document.__exitPointerLock();
        },

        /* 按鍵響應方法 */
        keydownEvent: function (event) {

            let t = h5Player
            let keyCode = lowerKeyCode(event.keyCode)
            let key = event.key.toLowerCase()
            let player = t.player()

            if (!player) {
                // no video tag
                return
            }

            let rootNode = getRoot(player);

            let keyAsm = (event.shiftKey ? SHIFT : 0) | (event.ctrlKey ? CTRL : 0) | (event.altKey ? ALT : 0);

            if (!keyAsm && keyCode == 27 && (document.fullscreenElement || rootNode.pointerLockElement)) {
                setTimeout(() => {
                    if (document.fullscreenElement) {
                        document.exitFullscreen();
                    } else if (document.pointerLockElement) {
                        h5Player._keydownEvent_leavePL();
                    }
                }, 700);
                return;
            }

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

            //console.log('K01')

            /* 切換插件的可用狀態 */
            // Shift-`
            if (keyAsm == SHIFT && keyCode === 192) {
                t.enable = !t.enable;
                t.tips(false);
                if (t.enable) {
                    t.tips('啟用h5Player插件')
                } else {
                    t.tips('禁用h5Player插件')
                }
                // 阻止事件冒泡
                event.stopPropagation()
                event.preventDefault()
                return false
            }
            if (!t.enable) {
                consoleLog('h5Player 已禁用~')
                return false
            }

            if (keyAsm == SHIFT && keyCode === 70) t.switchFakeUA();

            /* 非全局模式下,不聚焦則不執行快捷鍵的操作 */

            if (!keyAsm && key == 'enter' && !isInOperation()) {
                if (!rootNode.pointerLockElement && !document.fullscreenElement) {
                    if (rootNode.pointerLockElement != player) {
                        h5Player._keydownEvent_enterPL();

                        // 阻止事件冒泡
                        event.stopPropagation()
                        event.preventDefault()
                        return false
                    }
                } else if (rootNode.pointerLockElement && !document.fullscreenElement) {
                    h5Player._keydownEvent_leavePL();

                    // 阻止事件冒泡
                    event.stopPropagation()
                    event.preventDefault()
                    return false
                } else if (document.fullscreenElement) {
                    document.exitFullscreen();

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

            let hv = (elm) => (elm && (elm == player || elm.contains(player)) ? elm : null);
            //console.log('cE0',document.activeElement)

            let _checkingPass;

            let plm = null;
            if (rootNode.activeElement && !rootNode.pointerLockElement && !document.fullscreenElement) {
                // the active element may or may not contains the player
                // but the player box (player->parent->parent->...) shall contains the active element (and the player)
                // so if the active element is inside the layoutbox, okay!
                // ps. layoutbox may be much larger than the activeelement, then it is not overlapping case.
                plm = rootNode.activeElement
                _checkingPass = _checkActiveNode(rootNode.activeElement, player);
            } else {
                plm = hv(rootNode.pointerLockElement) || hv(document.fullscreenElement)
                _checkingPass = !!plm
            }

            //console.log('K02')

            if (_checkingPass) {

                if (isInOperation()) return

                t.keyCodeList = t.keyCodeList || Object.values(t.keyMap);
                t.keyList = t.keyList || (Object.keys(t.keyMap).concat(['enter', 'shift', 'control', 'alt', 'escape', ' ', 'space', 'arrowleft', 'arrowright', 'arrowright', 'arrowup', 'arrowdown', '|']));
                //console.log(t.keyCodeList,keyCode, t.keyList, key)
                let isInUseCode = t.keyCodeList.includes(keyCode) || t.keyList.includes(key);
                if (!isInUseCode) return

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

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

            }
        },
        /* 設置播放進度 */
        setPlayProgress: function (player, curTime) {
            if (!player) return
            let t = h5Player
            if (!curTime || Number.isNaN(curTime)) return
            player.currentTime = curTime
            if (curTime > 3) {
                t.tips(false);
                t.tips(`Playback Jumps to ${tool.formatCT(curTime)}`)
                if (player.paused) player.play();
            }
        }
    }

    function _add_filter(rootElm) {
        if (rootElm && rootElm.querySelector && !rootElm.querySelector('#_h5player_section_')) {

            let svgFilterElm = document.createElement('section')
            svgFilterElm.id = '_h5player_section_'
            svgFilterElm.innerHTML = `
<svg id='_h5p_image' version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="_h5p_sharpen1">
<feConvolveMatrix filterRes="100 100" style="color-interpolation-filters:linearrgb" order="3" kernelMatrix="` + `
-0.3 -0.3 -0.3
-0.3 3.4 -0.3
-0.3 -0.3 -0.3`.replace(/[\n\r]+/g, '  ').trim() + `"  preserveAlpha="true"/>
</filter>
<filter id="_h5p_unsharpen1">
<feConvolveMatrix style="color-interpolation-filters:sRGB;color-rendering: optimizeQuality;color-interpolation: sRGB;" order="5" kernelMatrix="` + `  -0.00391  -0.01563  -0.02344  -0.01563  -0.00391
-0.01563  -0.06250  -0.09375  -0.06250  -0.01563
-0.02344  -0.09375   1.85980  -0.09375  -0.02344
-0.01563  -0.06250  -0.09375  -0.06250  -0.01563
-0.00391  -0.01563  -0.02344  -0.01563  -0.00391`.replace(/[\n\r]+/g, ' ').trim() + `"  preserveAlpha="true"/>
</filter>
<filter id="_h5p_grey1">
<feColorMatrix values="0.3333 0.3333 0.3333 0 0
0.3333 0.3333 0.3333 0 0
0.3333 0.3333 0.3333 0 0
0      0      0      1 0"/>
<feColorMatrix type="saturate" values="0" />
</filter>
</defs>
</svg>
`;
            //document.a=svgFilterElm;
            //document.b=rootElm;
            rootElm.appendChild(svgFilterElm);
        }

    }

    function videoDOMFound(video) {

        if (video.getAttribute('_h5p_video_init_')) return;
        video.setAttribute('_h5p_video_init_', '1');

        let rootNode = getRoot(video);


        if (rootNode.host) {


            h5Player.getLayoutBox(video);


        }

        let rootElm = rootNode.querySelector('head') || rootNode.querySelector('html') || document.documentElement //48763
        _add_filter(rootElm)

        video.addEventListener('loadedmetadata', handle.player_videoLoaded, true);

    }

    /**
     * 某些網頁用了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
     */

    const eventHandler = async(evtKey, details) => {
        let v_sub;
        switch (evtKey) {
            case 'addEventListener':
                'N/A';
                break;
            case 'removeEventListener':
                'N/A';
                break;
            case 'attachShadow':
            case 'createShadowRoot':
                if (details.shadowRoot) {
                    if (_shadowDomList_ && _shadowDomList_.push) _shadowDomList_.push(details.shadowRoot)
                    v_sub = new VideoListener(details.shadowRoot);
                    v_sub.listen(wVideo)
                    h5Player.bindKeyEvents(details.shadowRoot);
                }
                break;
            default:
        }

    }

    function hackAttachShadow() {
        if (!window.HTMLElement) throw "h5Player: HTMLElement not supported";
        if (!window.HTMLElement.prototype) throw "h5Player: HTMLElement.prototype not supported";
        if (!('attachShadow' in window.HTMLElement.prototype)) throw "h5Player: HTMLElement.prototype.attachShadow not supported";

        if (HTMLElement.prototype._hasHackAttachShadow_) return;
        HTMLElement.prototype._hasHackAttachShadow_ = true;

        window.HTMLElement.prototype._attachShadow = window.HTMLElement.prototype.attachShadow

        window.HTMLElement.prototype.attachShadow = function () {
            let arg = Array.prototype.slice.call(arguments);
            if (arg[0] && arg[0].mode) arg[0].mode = 'open';
            let elm = this;
            let shadowRoot = HTMLElement.prototype._attachShadow.apply(elm, arg)
            eventHandler('attachShadow', {
                shadowRoot
            })
            return shadowRoot
        }

    }

    function hackCreateShadowRoot() {

        if (!window.HTMLElement) throw "h5Player: HTMLElement not supported";
        if (!window.HTMLElement.prototype) throw "h5Player: HTMLElement.prototype not supported";
        if (!('createShadowRoot' in window.HTMLElement.prototype)) throw "h5Player: HTMLElement.prototype.createShadowRoot not supported";

        if (HTMLElement.prototype._hasHackCreateShadowRoot_) return;
        HTMLElement.prototype._hasHackCreateShadowRoot_ = true;

        HTMLElement.prototype._createShadowRoot = HTMLElement.prototype.createShadowRoot;

        HTMLElement.prototype.createShadowRoot = function () {
            let elm = this;
            const shadowRoot = HTMLElement.prototype._createShadowRoot.apply(elm, arguments);
            eventHandler('createShadowRoot', {
                shadowRoot
            });
            return shadowRoot;
        }
    }

    /* 事件偵聽hack */
    function hackEventListener() {
        if (!window.EventTarget) return
        const EVENT = window.EventTarget.prototype
        if (EVENT._addEventListener) return

        function Listeners(dom, type) {
            this._dom = dom;
            this._type = type;
            this.listenersCount = 0;
            this.hashList = {};
        };
        Listeners.prototype = new Object;
        Listeners.prototype.__defineGetter__("baseFunc", function () {
            if (this._dom && this._type) {
                return this._dom['on' + this._type];
            }
        });
        Listeners.prototype.__defineGetter__("funcCount", function () {
            if (this._dom && this._type) {
                return (typeof this.baseFunc == 'function') * 1 + (this.listenersCount || 0)
            }
        });

        EVENT._addEventListener = EVENT.addEventListener
        EVENT._removeEventListener = EVENT.removeEventListener
        // hack addEventListener
        EVENT._evtCount = 0;

        const _EVENT_raf = ((elm) => {

            if (!elm._timeupdatef_hasListen) {

                elm._timeupdatef_hasListen = true;

                if (!_endlessloop) {
                    _endlessloop = new EndlessLoop();
                }

                let opts = _endlessloop.append(handle.timeupdatef_ell)
                opts.video = elm;
                opts.lastTime = -1;
                opts.evt = new SimEvent("timeupdatef", {
                    bubbles: false,
                    cancelable: true,
                }, 'timeupdate');

                opts.loopingStart();
                _ell_timeupdatefs.push(opts);

            }

        });

        EVENT.addEventListener = function () {
            if (!this || !(this instanceof EventTarget)) {
                return EVENT._addEventListener.apply(this, arguments)
                //unknown bug?
            }

            let arg = arguments
            let type = arg[0]
            let listener = arg[1]
            let boolCapture = !!((arg[2] && typeof arg[2] == 'object') ? arg[2].capture : arg[2])
            let sim_arg = null;
            if (type == 'timeupdate' && this.nodeType == 1 && this.nodeName == "VIDEO") {
                consoleLog('timeupdate detected')
                sim_arg = Array.prototype.slice.call(arg);
                _EVENT_raf(this);
                sim_arg[0] = 'timeupdatef';
                sim_arg[1] = sim_arg_1_fn(sim_arg[1], _evtOB_create._timeupdate())
                EVENT._addEventListener.apply(this, sim_arg)
                consoleLog('timeupdate(fast) is added with listener', fn_toString(listener))
            } else {
                EVENT._addEventListener.apply(this, arg)
            }

            this._listeners = this._listeners || {}
            this._listeners[type] = this._listeners[type] || new Listeners(this, type)
            let addedTime = +new Date().getTime();
            let uid = 100000 + (++EVENT._evtCount);
            let listenerObj = {
                //target: this,
                //type,
                listener,
                options: arg[2],
                uid: uid,
                useCapture: boolCapture,
                sim_arg
                //addedTime:addedTime
            }
            this._listeners[type].hashList[uid + ''] = listenerObj;
            this._listeners[type].listenersCount++;
        }
        // hack removeEventListener
        EVENT.removeEventListener = function () {
            if (!this || !(this instanceof EventTarget)) {
                return EVENT._removeEventListener.apply(this, arguments)
                //unknown bug?
            }

            let arg = arguments
            let type = arg[0]
            let listener = arg[1]

            let boolCapture = !!((arg[2] && typeof arg[2] == 'object') ? arg[2].capture : arg[2])

            let defaultRemoval = true;
            if (this._listeners && this._listeners[type] && this._listeners[type].hashList) {
                let hashList = this._listeners[type].hashList
                for (let k in hashList) {
                    if (hashList[k].listener === listener && hashList[k].useCapture == boolCapture) {
                        if (hashList[k].sim_arg) {
                            defaultRemoval = false;
                            EVENT._removeEventListener.apply(this, hashList[k].sim_arg.slice(0, 3));
                        }
                        delete hashList[k];
                        this._listeners[type].listenersCount--;
                        break;
                    }
                }
            }
            if (defaultRemoval) EVENT._removeEventListener.apply(this, arg);
        }
    }

    function wVideo(video) {

        if (!video) return;
        if (video.getAttribute('_h5ppid')) return;
        consoleLog('wVideo', video)

        wVideo._readyCount++;
        /* 檢測是否存在H5播放器 */

        let videoCount = wVideo._readyCount;
        consoleLog(' - HTML5 Video is detected -', `Number of Videos: ${videoCount}`)

        let t = h5Player;

        t.init_count = (t.init_count || 0) + 1;
        if (t.init_count === 1) t.fireGlobalInit();

        video.setAttribute('_h5ppid', 'h5p-' + t.init_count)
        videoDOMFound(video)

    }

    wVideo._readyCount = 0;

    hackAttachShadow()
    hackCreateShadowRoot()
    hackEventListener()

    /* 檢測到有視頻標籤就進行初始化 */
    window.addEventListener('message', handle.win_receiveMsg, false);
    let v_main = new VideoListener(document.documentElement);
    v_main.listen(wVideo)

    let ls_res = []
    try {
        ls_res = [!!window.localStorage, !!window.top.localStorage]
    } catch (e) {}

    try {
        let winp=window;
        let winc=0;
        while(winp!==window.top && winp &&++winc)winp=winp.parentNode;
        ls_res.push(winc)
    } catch (e) {}

    consoleLogF('- h5Player Plugin Loaded -', ...ls_res)

    function isInCrossOriginFrame() {
        let result = true;
        try {
            if (window.top.localStorage || window.top.location.href) {
                result = false;
            }
        } catch (e) {
            result = true;
        }
        return result
    }

    if (isInCrossOriginFrame()) consoleLog('cross origin frame detected');

})();
/*
(function () {
    return;

    const isIframe = (window.top !== window.self && window.top && window.self);

    function isInIframe() {
        return window !== window.top
    }

    function isInCrossOriginFrame() {
        let result = true;
        try {
            if (window.top.localStorage || window.top.location.href) {
                result = false;
            }
        } catch (e) {
            result = true;
        }
        return result
    }

    function createDebugMethod(name) {
        name = name || 'info';
        return function () {
            const arg = Array.from(arguments);
            arg.unshift('h5player debug message :');
            console[name].apply(console, arg);
        }
    }

    //const debug = {};

    const h5Player = {
        bindEvent: function () {
            const t = this;
            if (t._hasBindEvent_) return
            t._hasBindEvent_ = true;

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

            if (isInIframe() && !isInCrossOriginFrame()) {
                window.top.document.removeEventListener('keydown', t.keydownEvent, true);
                window.top.document.addEventListener('keydown', t.keydownEvent, true);
            }
        },
    };

    try {
        h5Player.bindEvent();

    } catch (e) {
        //debug.error(e);
    }

    //debug.log = consoleLog
    //debug.error = consoleLog

    //debug.log(1211)

})(); */