Greasy Fork镜像 is available in English.

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 04. 10. 2019. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         HTML5 Video Player Enhance
// @version      2.8.0b4
// @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
// @grant        GM_addStyle
// @require https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js
// @namespace https://greasyforks.org/users/371179
// ==/UserScript==
(function() {
    'use strict';
    // Your code here...

    let h5Player;

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

    const _sVersion_ = 1816;

    //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 = (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 = [...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

    }

    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 postMsg() {
        const [tag,...data] = arguments;
        if (typeof tag == 'string') {
            window.parent.postMessage({
                tag,
                data
            }, '*');
        }
    }


    function consoleLog() {
        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);
            console.log(`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);
            console.log(`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);
            console.log(`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)
            }
        },
        _isInteractionBox_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 = 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;

        },
        _isInteractionBox: 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 >= parentCount) return;
            let pElr = walkRes[pEl_idx]

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


        },
        getInteractionBox: function(vEl, pEl) {

            if (!pEl) return null;

            let walkRes = domTool._isInteractionBox_1(vEl, pEl);



            let parentCount = walkRes.length;


            if (parentCount-1>=0 && domTool._isInteractionBox(vEl, walkRes, parentCount - 1)) return walkRes[parentCount - 1].elm;
            if (parentCount-2>=0 && domTool._isInteractionBox(vEl, walkRes, parentCount - 2)) return walkRes[parentCount - 2].elm;

            return vEl;


        },
        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) {
                let style = document.createElement('style');
                style.type = 'text/css';
                let node = document.createTextNode(css);
                style.appendChild(node);
                document.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 shallShowTips = this._volume>=0;  //prevent initialization
            
            if(!this.volume) {
                //if(!this.muted) h5Player.tips('Mute: On');
                this.muted=true;
            }else if(this.muted){
                this.muted=false;
                this._volume=this.volume;
                //h5Player.tips('Mute: On');
                
            }else if(!this.muted){
                this._volume=this.volume;
            }
            console.log('volume changed')
            
            let player = this;
            let t = h5Player;
            
            
            if(shallShowTips)
                t.tips('Volume: ' + parseInt(player.volume * 100) + '%', undefined, 3000)
            
            
        },
        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);
            }

        },
        player_mouseEnter: function(evt) {
            // h5Player._isFoucs = true
            //if(h5Player.playerInstance!==e.target){
            //h5Player.playerInstance = e.target
            //h5Player.onVideoTriggering()
            let player=evt.target;
            if(player.nodeName!="VIDEO") player=player.querySelector('video[_h5ppid]');
            if(player)
                h5Player.playerBoxInit(player)
            //}
            // consoleLog('player mouse ENTER')
        },
        player_mouseDown: function(evt) {
            // h5Player._isFoucs = true
            //if(h5Player.playerInstance!==e.target){
            //h5Player.playerInstance = e.target
            //h5Player.onVideoTriggering()
            let player=this;  //evt.target may be something else
            if(player.nodeName!="VIDEO") player=player.querySelector('video[_h5ppid]');
            if(!player)return;
            let {
               playerBox,
               wPlayer
            } = h5Player.getPlayerBox(player);
            
            if(playerBox&&(playerBox==evt.target||playerBox.contains(evt.target))){

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

        },
        player_playTriggering: function(evt) {


            h5Player.playerBoxInit(evt.target)

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

            //by default, when the video start playing, the playerbox can get the focus and activate the keyboard control
            //let {
            //    playerBox,
            //    wPlayer
            //} = h5Player.getPlayerBox(evt.target);
            //if (playerBox && playerBox.hasAttribute('tabindex')) playerBox.focus();
            
        },
        player_dblClick: function(evt) {
            console.log('dblclick', this, evt.target)
            let elm = (evt.target != this && this.contains(evt.target)) ? this : evt.target;
            //  www.tucao.one   ;  evt.target = bullet curtain;  this = interactionbox

            let vpid = elm.getAttribute('_h5p_interactionbox_') || elm.getAttribute('_h5ppid') || null;
            if (!vpid) return;
            let player = elm.ownerDocument.querySelector(`[_h5ppid="${vpid}"]`);
            h5Player.playerBoxInit(player)
            h5Player.playerInstance = player
            h5Player.onVideoTriggering()
            
            h5Player.getFocus(player,evt)

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


            h5Player.callFullScreenBtn();

            // 阻止事件冒泡
            //event.stopPropagation()
            // event.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 = e.data;
            if (typeof e.data == 'object' && typeof e.data.tag == 'string') {
                tag = e.data.tag;
            }
            switch (tag) {
                case 'consoleLog':
                    console.log.apply(console, e.data.data)
                    break;

            }
        },
        timeupdatef_ell: async function(opts) {
            let video = opts.video;
            let time = video.currentTime;
            if (time !== opts.lastTime) {
                video._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, JSON.stringify({
                    '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;
            consoleLog('playing')
            if (player._isThisPausedBefore_) requestAnimationFrame(() => h5Player.tips('Playback resumed', undefined, 2500))


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


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

            if (!player._record_continuous) {

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



                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());




        },
        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))


            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 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
                    console.log(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 {
                    playerBox,
                    wPlayer
                } = h5Player.getPlayerBox(player)
                const container = playerBox.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 {
                    playerBox,
                    wPlayer
                } = h5Player.getPlayerBox(player)
                const container = playerBox.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
            })();


            console.log('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
            console.log('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.rootElement = shadowRoot || window.document;
            this.observer = null;
            this._check = () => this.check();

        }
        listen(fn) {
            this.init_observer();
            this.fn = fn; // single function
            this.check();
        }
        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) {
                        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 check() {

            let videos = this.rootElement.getElementsByTagName('VIDEO');
            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;

    }

    /**
     * 某些網頁用了attachShadow closed mode,需要open才能獲取video標籤,例如百度雲盤
     * 解決參考:
     * https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed
     * https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension
     */


    function hackAttachShadow() {
        if (window._hasHackAttachShadow_) return
        window._hasHackAttachShadow_ = true
        let err = null;

        function errFunc(_err) {
            console.error('hackAttachShadow error by h5player plug-in', {
                errCode: err = _err
            })
            return
        }
        window._shadowDomList_ = []
        if (!window.Element) return errFunc(0x1E01);
        if (!window.Element.prototype) return (0x1E02);
        window.Element.prototype._attachShadow = window.Element.prototype.attachShadow

        window.Element.prototype.attachShadow = function() {

            let arg = arguments
            try {

                if (arg[0] && arg[0]['mode']) {
                    // 強制使用 open mode
                    arg[0]['mode'] = 'open'
                }
            } catch (e) {
                return errFunc(0x3001);
            }
            let shadowRoot = this._attachShadow.apply(this, arg)

            // 存一份shadowDomListf
            if (window._shadowDomList_ && window._shadowDomList_.push) window._shadowDomList_.push(shadowRoot)


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

            } catch (e) {
                return errFunc(0x3000);
            }
            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 (!_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") {
                console.log('timeupdate detected')
                sim_arg = [...arg];

                _EVENT_raf(this);
                sim_arg[0] = 'timeupdatef';
                sim_arg[1] = (function(f, evtOB) {
                    return async function(e) {
                        let o = new evtOB(e);
                        let arg = [...arguments]
                        arg[0] = o
                        //console.log('success',[...arg])
                        return f.apply(this, arg)
                    }
                })(sim_arg[1], _evtOB_create(Event, {
                    isTrusted: true,
                    type: 'timeupdate'
                }))
                EVENT._addEventListener.apply(this, sim_arg)

                //console.log('listener',listener)
                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);
        }
    }



    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  : function (arr) {

             * 向上查找操作
             * @param dom {Element} -必選 初始dom元素
             * @param fn {function} -必選 每一級ParentNode的回調操作
             * 如果函數返回true則表示停止向上查找動作

                function _quickSort(arr){

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

                return _quickSort(arr)
            },*/
        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);
    }

    h5Player = {
        /* 提示文本的字號 */
        fontSize: 16,
        enable: true,
        playerInstance: null,
        scale: 1,
        translate: {
            x: 0,
            y: 0
        },
        playbackRate: 1,
        /* 快進快退步長 */
        skipStep: 5,
        /* 獲取當前播放器的實例 */
        player: function() {
            let t = this
            return t.playerInstance || t.getPlayerList()[0]
        },
        /* 每個網頁可能存在的多個video播放器 */
        getPlayerList: function() {
            let list = [...document.querySelectorAll('video')]
            // 被封裝在 shadow dom 裡面的video
            if (window._shadowDomList_) {
                window._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 playerBox = player.getBoundingClientRect()
            if (playerBox.width && playerBox.height) {
                domTool.eachParentNode(player, function(parent) {
                    if (parent === document || !parent.getBoundingClientRect) return
                    let parentBox = parent.getBoundingClientRect()
                    if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) {
                        wrapDom = parent
                    }
                })
            }
            return wrapDom
        },
        getFocus:function(player,evt){
            setTimeout(function(){
                let rpid=player.getAttribute('_h5ppid');
                let interactionBox=player.ownerDocument.querySelector(`[_h5p_interactionbox_="${rpid}"]`);
                
                //console.log('p',rpid, player,interactionBox,document.activeElement)
                if(interactionBox && interactionBox!=document.activeElement && !interactionBox.contains(document.activeElement)){
                    console.log('make focus on', interactionBox)
                    //interactionBox.focus();
                    
                }
                
            },300)
        },
        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 = Store.read(k3n + '+' + m2vMax);
                        if (!_json_recordedTime) _json_recordedTime = {};
                        else _json_recordedTime = JSON.parse(_json_recordedTime);

                        if (typeof _json_recordedTime == 'object') recordedTime = _json_recordedTime;
                        else recordedTime = null;

                        console.log(recordedTime)
                        recordedTime = typeof recordedTime == 'object' ? recordedTime.t : recordedTime;
                        console.log(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) {
                        console.log('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, JSON.stringify({
                            '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);



            })




            player.removeEventListener('playing', handle.srcLoaded_playing);
            player.addEventListener('playing', handle.srcLoaded_playing);


            player.removeEventListener('pause', handle.srcLoaded_pause);
            player.addEventListener('pause', handle.srcLoaded_pause);
            
            h5Player.playerBoxInit(player);
            



        },
        fireGlobalInit: function() {

            let t = this

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

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


            h5Player.isLostFocus = null;
            consoleLog('keydown bind')
            document.removeEventListener('keydown', t.keydownEvent)
            document.removeEventListener('keydown', t.keydownEvent, true)
            document.addEventListener('keydown', t.keydownEvent, true)
            /* 相容iframe操作 */
            async function bindTopWindow() {
                //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)
                    //iframe may not be able to control top window
                    if (window.top !== window) {
                        topDoc.removeEventListener('keydown', t.keydownEvent)
                        topDoc.removeEventListener('keydown', t.keydownEvent, true)
                        topDoc.addEventListener('keydown', t.keydownEvent, true)
                    }
                }
            }
            bindTopWindow()
            consoleLog('bindTopWindow passed')

            Store.clearInvalid(_sVersion_)


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

            /*

            document.addEventListener('mousedown',function(evt){
                var videos=document.querySelectorAll('video[_h5player_tips]');
            [...videos].forEach((video)=>{
                var rect=video.getBoundingClientRect()

                var b=(evt.clientX>=rect.left && evt.clientX<=rect.right)
                && (evt.clientY>=rect.top && evt.clientY<=rect.bottom)

                if(b){

                    //__activeElementsToggles.push([document.activeElement, video])

                    document.activeElement.setAttribute('_h5p_active_on',video.getAttribute('_h5player_tips'));

                    //console.log('yeah',video, document.activeElement,evt.target)
                }

            })

            },true)*/


        },
        playerBoxInit: function(player) {

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

            let {
                playerBox,
                wPlayer
            } = h5Player.getPlayerBox(player)
            let interactionBox = domTool.getInteractionBox(player, playerBox);

            if (interactionBox && interactionBox.getAttribute('_h5p_interactionbox_') != vpid) {


                console.log('D-1', interactionBox)

                for (let elm_of_vpid of player.ownerDocument.querySelectorAll(`[_h5p_interactionbox_="${vpid}"]`)) {
                    //change of parentNode
                    elm_of_vpid.removeAttribute('_h5p_interactionbox_');
                    elm_of_vpid.removeEventListener('dblclick', handle.player_dblClick, true)
                    elm_of_vpid.removeEventListener('mouseenter', handle.player_mouseEnter)
                    elm_of_vpid.addEventListener('mousedown', handle.player_mouseDown)
                }

                interactionBox.setAttribute('_h5p_interactionbox_', vpid)

                if (!interactionBox.hasAttribute('tabindex')) interactionBox.setAttribute('tabindex', '-1');

                interactionBox.addEventListener('dblclick', handle.player_dblClick, true)
                interactionBox.addEventListener('mouseenter', handle.player_mouseEnter)
                interactionBox.addEventListener('mousedown', handle.player_mouseDown)

            }


        },
        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))
        },
        getPlayerBox: function(player, skipPlayer) {
            //without checkActiveBox, just a DOM for you to append tipsDom

            let playerBox = null,
                wPlayer = null


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

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


            while (playerBox && playerBox.nodeType == 1 && playerBox.offsetHeight == 0) playerBox = playerBox.parentNode;
            while (playerBox && playerBox.nodeType == 1 && playerBox.offsetHeight < player.offsetHeight) playerBox = playerBox.parentNode;

            // playerBox is a node contains <video> and offsetHeight>=video.offsetHeight
            // or null

            // you can insert the element here;

            playerBox = playerBox || player.parentNode
            if (playerBox && playerBox.nodeType == 1) {

                wPlayer = player;
                while (wPlayer.parentNode != playerBox) wPlayer = wPlayer.parentNode;

            } else {
                playerBox = player.parentNode
                wPlayer = player;
                if (playerBox.nodeType == 1 && playerBox.offsetHeight >= wPlayer.offsetHeight) {

                } else {
                    playerBox = null;
                    wPlayer = null;
                }

            }
            return {
                playerBox,
                wPlayer
            };

        },
        toParentContains: function(playerBox, selector) {

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

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

            let box1 = elm1;
            let box2 = elm2;

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

            let playerBox = null;

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

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

            return playerBox

        },
        change_playerBox: function(tipsDom) {
            let t = h5Player;
            let player = t.player()
            let {
                playerBox,
                wPlayer
            } = t.getPlayerBox(player)
            //console.log(playerBox,wPlayer)
            //console.log('cp',playerBox)
            if (playerBox && playerBox.nodeType == 1) {
                if (!tipsDom.parentNode || tipsDom.parentNode !== playerBox) {
                    console.log('changed_playerBox')
                    playerBox.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(playerBox, 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 player.ownerDocument.querySelector('#' + tcn)
        },
        setWebFullScreen: function() {



            //return this.callFullScreenBtn7();
            let t = this;
            let player = t.player()
            let tipsDom = t.getPlayerTips();
            /*
            let playerBox = t.getPlayerCTBox(player,tipsDom);




            function get_gPlayer() {
                // parent of same size as video player
                let q1 = JSON.stringify(player.getBoundingClientRect())
                let pPlayer = player
                let qPlayer = playerBox
                for (let q3 = 0; q3 < 4; q3++) {
                    if (!qPlayer) break;
                    let q2 = JSON.stringify(qPlayer.getBoundingClientRect())
                    if (q1 == q2) {
                        pPlayer = qPlayer
                        qPlayer = qPlayer.parentNode
                    } else {
                        //console.log(player,q1);
                        //console.log(qPlayer,q2);
                        break;
                    }
                }
                let gPlayer = pPlayer
                return gPlayer;
            }
            let gPlayer = get_gPlayer();
            */
            let {
                playerBox,
                wPlayer
            } = t.getPlayerBox(player);
            let gPlayer = t.getPlayerCTBox(playerBox, tipsDom);
            console.log('gPlayer', gPlayer)

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

            if (chFull === true) {
                consoleLog('chFull', 'true')
                player.ownerDocument.exitFullscreen();
            } else {
                consoleLog('chFull', 'false')
                consoleLog('DOM fullscreen')
                gPlayer.requestFullscreen()
            }

        },
        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 = player.ownerDocument.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 playerBox = t.getPlayerCTBox(player, tipsDom);

                let pPlayer = player
                let qPlayer = playerBox
                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.contains = 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,
                                visible: null,
                                click: null,
                                childElementCount: null,
                                contains: null
                            }
                        });
                        if (('_listeners' in document)) {
                            gs1.forEach(function(elp) {
                                let elm = elp.elm;
                                elp.click = 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 ('getBoundingClientRect' in player) {
                            gs1.forEach(function(elp) {
                                let elm = elp.elm;
                                let rect = elm.getBoundingClientRect();
                                elp.visible = rect.height * rect.width > 0
                            })
                        }
                        gs1 = gs1.filter(function(elp) {
                            return elp.click
                        })
                        //console.log('gs1',gs1)
                        // -- inter-elm --
                        if ('contains' in player) {
                            gs1.forEach(_gs1_contains)
                        }
                        let gs2 = gs1.filter(function(elp) {
                            return !elp.contains && elp.visible
                        })
                        console.log('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) {
            if (!amount) return
            let _amount = +(+amount).toFixed(1);
            if (!_amount || isNaN(_amount)) return;
            let t = this;
            let player = t.player();
            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;
                }
            }

            player.currentTime += _amount; // negative
            let changed = true;
            flagTips = (flagTips < 0) ? false : (flagTips > 0) ? true : changed;
            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)
            if (!_amount || isNaN(_amount)) return;
            let t = this
            let player = t.player()
            if (_amount > 0) {
                if (player.volume < 1) {
                    player.volume += _amount // positive
                }
            } else {
                if (player.volume > 0) {
                    player.volume += _amount // negative
                }
            }
            t.tips('Volume: ' + parseInt(player.volume * 100) + '%', undefined, 3000)
        },
        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()
                }
            } else {
                if (taskConf.pause) {
                    TCC.doTask('pause')
                } else {
                    player.pause()

                }
            }
        },
        tipsClassName: 'html_player_enhance_tips',
        tips: function(str, duration, order) {
            let t = h5Player
            let player = t.player()
            if (!player) {
                console.log('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 = player.ownerDocument.querySelector(tipsSelector)
            if (!tipsDom) {
                //console.log(tipsSelector)
                //console.log(player.ownerDocument.querySelector(tipsSelector))
                console.log('init h5player tips dom error...')
                return false
            }
            t.change_playerBox(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._playerBoxOBJ = h5Player.getPlayerBox(player)
                    tipsDom._env_changed = 0;


                    if (!_endlessloop) {
                        _endlessloop = new EndlessLoop();
                    }
                    tipsDom._playerBoxOBJ_p = 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._playerBoxOBJ) return;
                                let {
                                    playerBox,
                                    wPlayer
                                } = tipsDom._playerBoxOBJ
                                if (playerBox == null || wPlayer == null) return;
                                let p = [~~playerBox.offsetWidth, ~~playerBox.offsetHeight, ~~wPlayer.offsetWidth, ~~wPlayer.offsetHeight];

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

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



                                tipsDom._playerBoxOBJ_p = p;

                                if (!tipsDom.id) return;
                                let player = playerBox.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"]');
        },
        initTips: function() {
            /* 設置提示DOM的樣式 */
            let t = this
            let player = t.player()
            let parentNode = player.parentNode
            let tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName + '_' + (+new Date));
            player.setAttribute('_h5player_tips', tcn)
            if (player.ownerDocument.querySelector('#' + tcn)) return false;


            if (!t.notFirstTips) {
                t.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, ''))



            }



            let tipsDom = document.createElement('div')



            var transitionEvent = whichTransitionEvent('animation');


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

            tipsDom.id = tcn;
            tipsDom.setAttribute('unselectable', 'on');
            tipsDom.setAttribute('_potTips_', '0');
            t.change_playerBox(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 {playerBox,wPlayer}=h5Player.getPlayerBox(player,false);

                // console.log('playerBox', playerBox);
                // console.log('wPlayer', wPlayer);
                // console.log('odm', odm);
                // console.log('playerBoxW', playerBox.offsetWidth,odm.offsetWidth);
                // console.log('playerBoxH', playerBox.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 playerBox=h5Player.getPlayerBox(player)
                //resizeDOM.relativeParent(playerBox.parentNode,playerBox)


            }
            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 {
                    playerBox,
                    wPlayer
                } = h5Player.getPlayerBox(player, false);
                if (playerBox && wPlayer) tmp_elm = playerBox.parentNode != tipsDom._vpr_pn ? null : playerBox;

                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
            };
            //console.log('A00-coffset',customOffset)

            /*


            function FTC (scale=0,px=0){
                this.scale=scale;
                this.px=px;

            }
            FTC.prototype.plus=function(x){
                this.scale+=x.scale;
                this.px+=x.px

            }
            FTC.prototype.p=function(a,b){
                this.scale+=a;
                this.px+=b;

            }
            FTC.prototype.minus=function(x){
                this.scale-=x.scale;
                this.px-=x.px

            }
            FTC.prototype.m=function(a,b){
                this.scale-=a;
                this.px-=b;

            }
            FTC.prototype.mul=function(k){
                this.scale*=k;
                this.px*=k;

            }

            */



            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("#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()

                }
                // 進入或退出畫中畫模式
                else if (key === 'p') {
                    pictureInPicture(player)
                } else if (key == 'r') {
                    if (player._h5player_lastrecord_ !== null && (player._h5player_lastrecord_ >= 0 || player._h5player_lastrecord_ <= 0)){
                        t.setPlayProgress(player, player._h5player_lastrecord_)
                    }

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


                    }
                    let scale = t.scale = Number(t.scale).toFixed(1)
                    player.style.transform = `scale(${scale}) translate(${t.translate.x}px, ${t.translate.y}px)`
                    let tipsMsg = `視頻縮放率:${scale * 100}%`
                    if (t.translate.x) {
                        tipsMsg += `,水平位移:${t.translate.x}px`
                    }
                    if (t.translate.y) {
                        tipsMsg += `,垂直位移:${t.translate.y}px`
                    }
                    t.tips(tipsMsg)
                    return TERMINATE
                }
            }
            // 防止其它無關組合鍵衝突
            if (!keyAsm) {
                let kControl = null
                let newPBR, oldPBR;
                switch (keyCode) {
                        // 方向鍵右→:快進3秒
                    case 39:
                        t.tuneCurrentTime(t.skipStep)
                        break;
                        // 方向鍵左←:後退3秒
                    case 37:
                        t.tuneCurrentTime(-t.skipStep)
                        break;
                        // 方向鍵上↑:音量升高 1%
                    case 38:
                        h5Player.tips(false);
                        if((player.muted&&player.volume===0) && player._volume>0) {
                            
                            player.muted=false;
                            player.volume=player._volume;
                        }
                        t.tuneVolume(0.01)
                        break;
                        // 方向鍵下↓:音量降低 1%
                    case 40:
                    
                        h5Player.tips(false);
                        if((player.muted&&player.volume===0) && player._volume>0) {
                            
                            player.muted=false;
                            player.volume=player._volume;
                        }
                        t.tuneVolume(-0.01)
                        break;
                        // 空格鍵:暫停/播放
                    case h5Player.keyMap.space:
                        h5Player.tips(false);
                        t.switchPlayStatus()
                        break;
                        // 按鍵X:減速播放 -0.1
                    case h5Player.keyMap.x:
                        h5Player.tips(false);
                        if (player.playbackRate > 0) {
                            t.setPlaybackRate(player.playbackRate - 0.1)
                        }
                        break;
                        // 按鍵C:加速播放 +0.1
                    case h5Player.keyMap.c:
                        h5Player.tips(false);
                        if (player.playbackRate < 16) {
                            t.setPlaybackRate(player.playbackRate + 0.1)
                        }
                        break;
                        // 按鍵Z:正常速度播放
                    case h5Player.keyMap.z:
                        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)
                        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')
                        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')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        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')
                        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')
                        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')
                        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')
                        break;
                        // 按鍵Q:圖像復位
                    case h5Player.keyMap.q:
                        h5Player.tips(false);
                        t.filter.reset()
                        t.tips('Video Filter Reset')
                        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')
                        break;
                        // 按鍵迴車,進入FULLSCREEN
                    case h5Player.keyMap.enter:
                        //t.callFullScreenBtn();
                        break;
                    case h5Player.keyMap.n:
                        pictureInPicture(player);
                        break;
                    case h5Player.keyMap.m:
                        h5Player.tips(false);
                        //console.log('m!', player.volume,player._volume)
                                        
                            if(player.volume>=0){
                                
                                    
                                    
                                if(!player.volume) {
                                    
                                    if(!player._volume) player._volume = 0.5; // default volume = 0.5
                                    
                                    if(player._volume) {
                                        player.volume=player._volume;
                                        if(player.muted)
                                            h5Player.tips('Mute: Off', undefined, 3001);
                                        player.muted=false;
                                    }
                                    
                                }else if(this.muted){
                                    
                                    if(!player._volume) player._volume = 0.5; // default volume = 0.5
                                    
                                    if(player._volume) {
                                        player.volume=player._volume;
                                        player.muted=false;
                                        h5Player.tips('Mute: Off', undefined, 3001);
                                    }
                                    
                                    
                                }else if(!this.muted){
                                    
                                    
                                    player._volume=player.volume;
                                    player.volume=0;
                                    player.muted=true;
                                    h5Player.tips('Mute: On', undefined, 3001);

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

            if (document.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 keyAsm = (event.shiftKey ? SHIFT : 0) | (event.ctrlKey ? CTRL : 0) | (event.altKey ? ALT : 0);

            if (!keyAsm && keyCode == 27 && (document.fullscreenElement || document.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;
                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 (!document.pointerLockElement && !document.fullscreenElement) {
                    if (document.pointerLockElement != player) {
                        h5Player._keydownEvent_enterPL();

                        // 阻止事件冒泡
                        event.stopPropagation()
                        event.preventDefault()
                        return false
                    }
                } else if (document.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 (document.activeElement && !document.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 playerbox, okay!
                // ps. playerbox may be much larger than the activeelement, then it is not overlapping case.
                plm = document.activeElement



                _checkingPass = false;

                if (plm == player){
                    _checkingPass = true;
                }else{
                    let {
                        playerBox,
                        wPlayer
                    } = h5Player.getPlayerBox(player);
                    if (playerBox && playerBox.parentNode && (playerBox == plm || playerBox.contains(plm))) {
                        
                        
                        
                        let rpid=player.getAttribute('_h5ppid')||"NULL";
                        let interactionBox=playerBox.parentNode.querySelector(`[_h5p_interactionbox_="${rpid}"]`);    //the box can be playerBox
            //console.log('K03',rpid,interactionBox,playerBox)
                        if(interactionBox && (plm === interactionBox || interactionBox.contains(plm))) _checkingPass = true;
                    }
                }
            } else {

                plm = hv(document.pointerLockElement) || hv(document.fullscreenElement)

                _checkingPass = !!plm

                //let plms=[document.pointerLockElement,document.fullscreenElement].map(hv).filter(x=>!!x);

                //plms=plms.filter(plm=>plms.some(elm=>plm!=elm&&plm.contains(elm))?false:true)

                //plm=plms[0];



            }

            //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) {
                h5Player.tips(`Playback Jumps to ${tool.formatCT(curTime)}`)
                if (player.paused) player.play();
            }
        }
    }




    function videoDOMFound(video) {



        let rootElm = document.documentElement
        if (rootElm && rootElm.querySelector && !rootElm.querySelector('#_h5player_section_')) {



            let svgFilterElm = document.createElement('section')
            svgFilterElm.id = '_h5player_section_'
            svgFilterElm.innerHTML = `
<svg id='image' version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="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="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="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>
`;


            rootElm = rootElm.querySelector('head') || rootElm
            rootElm.appendChild(svgFilterElm);
        }



        /* 多video實例標籤的情況 */

        video.addEventListener('mouseenter', handle.player_mouseEnter) /* 鼠標移到其上面的時候重新指定實例 */
        //video.addEventListener('mouseleave',  handle.player_mouseLeave)
        video.addEventListener('playing', handle.player_playTriggering) /* 播放器開始播放的時候重新指向實例 */



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

        video.addEventListener('videoparentresize', function(evt) {
            console.log('videoparentresize')
            //fix

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

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

        });
        
        video.addEventListener("volumechange", handle.volume_changed);


    }

    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()
    hackEventListener()


    try {

        /* 檢測到有視頻標籤就進行初始化 */
        window.addEventListener('message', handle.win_receiveMsg, false);
        let v_main = new VideoListener(document.documentElement);
        v_main.listen(wVideo)
        /* 檢測shadow dom 下面的video */
        document.addEventListener('addShadowRoot', function(e) {
            let v_sub = new VideoListener(e.detail.shadowRoot);
            v_sub.listen(wVideo)
        })
    } catch (e) {
        console.error('h5player:', e)
    }

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。