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

// ==UserScript==
// @name         HTML5 Video Player Enhance
// @version      2.7.7(tw)
// @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
// @namespace https://greatest.deepsurf.us/users/371179
// ==/UserScript==
(function () {
    'use strict';
    // Your code here...

    let h5Player;

    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=(listener)=>{
        let str_listener=(listener+"");
        if(str_listener.length>110){
            str_listener=str_listener.substr(0,50)+' ... '+str_listener.substr(-50);
        }
    };

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

    let _ell_timeupdatefs=[];

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



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



    let gbl = {};
    let UA={};
    let tool={};





    let isIframe=false;

    if (window.top !== window.self && window.top && window.self) {
        isIframe = true;
    }



    function consoleLog(){

        if(isIframe) postMsgA('consoleLog',arguments);else console.log.apply(console,arguments);
    }

    function postMsg(){
        let arg = Array.prototype.slice.call(arguments)
        let tag = arg.shift();
        if(typeof tag == 'string'){
            let data = arg;
            window.parent.postMessage({tag,data},'*')
        }
    }


    function postMsgA(){
        let tag = arguments[0];
        if(typeof tag == 'string'){
            let data = Array.from(arguments[1]);
            window.parent.postMessage({tag,data},'*')
        }
    }


    let 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=(k+"").substr(0,20);
            let sv=(v+"").substr(0,20);
            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=(k+"").substr(0,20);
            let sv=(v+"").substr(0,20);
            console.log(`localStorage Read "${sk}" = "${sv}"`);
            return v;

        },
        remove:function(k){

            if(!Store.available())return false;
            Store.LS.removeItem(Store.prefix+k)
            let sk=(k+"").substr(0,20);
            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;

        }

    }



    let domTool = {
        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;
            },*/
        matchRule: function (str, rule) {
            return new RegExp("^" + rule.split("*").join(".*") + "$").test(str);
        },
        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;
                })
            }
        }
    };

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

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

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

        },
        player_mouseEnter:function (e) {
            h5Player._isFoucs = true
            h5Player.playerInstance = e.target
            h5Player.onVideoTriggering()
            consoleLog('player mouse ENTER')
        },
        player_mouseLeave: function (e) {
            h5Player._isFoucs = false
            consoleLog('player mouse leave')

        },
        player_playTriggering:function (event) {
            h5Player.playerInstance = event.target
            h5Player.onVideoTriggering()
        },
        doc_focusout:function (e) {
            let doc=this;
            h5Player.focusFxLock=true;
            requestAnimationFrame(function () {
                h5Player.focusFxLock=false;
                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 (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);
            }
        },
        pr_ell:async (opts)=>{

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



        },
        pr_loop:async function(){


            let player =  this.player;
            let t = h5Player;

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



            let progressMap = t.getPlayProgressMap();
            let list = Object.keys(progressMap);
            /* 只保存最近10個視頻的播放進度 */
            // when there are 18 records, delete 8 records, reduced to 10 records.
            if (list.length >= 18) {
                consoleLog(`stored progress: ${list.length} >= 18`)
                /* 根據更新的時間戳,取出最早添加播放進度的記錄項 */

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


                /* 刪除最早添加的記錄項 */
                mapN.forEach(function (item) {
                    delete progressMap[item.keyName]
                })

                consoleLog(`stored progress: reduced to 10`)
            }

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

            if(progressMap[_uid] && progressMap[_uid].progress==currentTimeToSave) shallSave=false;

            if(shallSave){

                /* 記錄當前播放進度 */
                progressMap[_uid] = {
                    progress: currentTimeToSave,
                    t: new Date().getTime()
                }
                /* 存儲播放進度表 */
                Store.save('_play_progress_', JSON.stringify(progressMap))

            }


        },
        pr_updateUID: function(){


            let player = this.player;

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


        },


        srcLoaded_playing:function(){
            let player = this;
            consoleLog('playing')
            if(player._isThisPausedBefore_)
                requestAnimationFrame(()=>h5Player.tips('Playback resumed',undefined,500))


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


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

            if(!player._record_continuous) {

                /* 同步之前設定的播放速度 */
                h5Player.setPlaybackRate()
                player._record_continuous=_endlessloop.append(handle.pr_ell);
                player._record_continuous.player=player
                player._record_continuous.timeDelta=2000;

                player._record_continuous.pTime=0;
                player._record_continuous.loop=handle.pr_loop;
                player._record_continuous.updateUID=handle.pr_updateUID;
                player._record_continuous.updateUID();

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


                if(!player._record_continuous.looping){
                    player._record_continuous.pTime=0;
                    player._record_continuous.loopingStart();
                }
            }


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


        },
        srcLoaded_pause:function(){
            let player = this;
            consoleLog('pause')
            player._isThisPausedBefore_=true;
            requestAnimationFrame(()=>h5Player.tips('Playback paused',undefined,500))


            if(player._record_continuous&&player._record_continuous.looping){

                player._record_continuous.loopingStop();
            }

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


        },
    };
    let pictureInPicture = function (videoElm) {
        if (document.pictureInPictureElement) document.exitPictureInPicture();
        else {
            ('requestPictureInPicture' in videoElm)?videoElm.requestPictureInPicture():gbl.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();
                const container = h5Player.getPlayerBox(player).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();
                const container = h5Player.getPlayerBox(player).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^']
        }

    ];

    ;let 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
        }
    }


    function ready(selector, fn, shadowRoot) {
        //only for video elements


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

        let addedVideoNodes=[]

        function check() {


            let mutations=arguments[0]||null


            let requireChecking=true;
            let mi=0;
            if(mutations){
                mutations.forEach(mutation=>mutation.addedNodes.forEach( node=>{if(node.tagName=='VIDEO')addedVideoNodes[mi++]=node;} ))
                if(mi==0) requireChecking = false;
            }

            if(requireChecking){

                let addedVideoNodes2
                if(mi>0){
                    addedVideoNodes2=addedVideoNodes.slice(0,mi)
                }

                //mutationsAddedOnly=mutations.filter(mutation=>mutation.addedNodes.length>0)

                for (let i = 0; i < listeners.length; i++) {
                    let listener = listeners[i]
                    let elements=mutations?addedVideoNodes2.filter( elm=>elm.matches(listener.selector) ):Array.from(doc.querySelectorAll('video'))

                    consoleLog(elements.length,30)
                    for (let j = 0; j < elements.length; j++) {
                        let element = elements[j]
                        if (!element._isMutationReady_) {
                            element._isMutationReady_ = true
                            element.setAttribute('h5play_observed','1');
                            listener.fn.call(element, element)
                        }
                    }
                }

            }
        }

        // 儲存選擇器和回調函數
        listeners.push({
            selector: selector,
            fn: fn
        })

        if(listeners.length==1){
            observer = new MutationObserver(check)
            consoleLog('q5q',location.href,document.hidden)
            observer.observe(shadowRoot || doc.documentElement, {
                childList: true,
                subtree: true
            })
        }
        check()
    }

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


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

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


                })
                _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() {
        let arg = Array.from(arguments)
        arg.unshift('h5player debug message :')
        console.info.apply(console, arg)
    }
    h5Player = {
        /* 提示文本的字號 */
        fontSize: 16,
        enable: true,
        globalMode: true,
        playerInstance: null,
        scale: 1,
        translate: {
            x: 0,
            y: 0
        },
        playbackRate: 1,
        /* 快進快退步長 */
        skipStep: 5,
        /* 獲取當前播放器的實例 */
        player: function () {
            let t = this
            return t.playerInstance || t.getPlayerList()[0]
        },
        /* 每個網頁可能存在的多個video播放器 */
        getPlayerList: function () {
            let list = []

            function findPlayer(context) {
                context.querySelectorAll('video').forEach(function (player) {
                    list.push(player)
                })
            }
            findPlayer(document)
            // 被封裝在 shadow dom 裡面的video
            if (window._shadowDomList_) {
                window._shadowDomList_.forEach(function (shadowRoot) {
                    findPlayer(shadowRoot)
                })
            }
            return list
        },
        getPlayerWrapDom: function () {
            let t = this
            let player = t.player()
            if (!player) return
            let wrapDom = null
            let playerBox = player.getBoundingClientRect()
            domTool.eachParentNode(player, function (parent) {
                if (parent === document || !parent.getBoundingClientRect) return
                let parentBox = parent.getBoundingClientRect()
                if (parentBox.width && parentBox.height) {
                    if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) {
                        wrapDom = parent
                    }
                }
            })
            return wrapDom
        },
        videoSrcFound:function(player){

            // src loaded

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


            player._isThisPausedBefore_=false;





            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);
            player.setAttribute('_h5pUID_', _uid)




            let recordedTime=null;
            let progressMap = t.getPlayProgressMap(player)

            let keyName = _uid
            if (keyName&&progressMap[keyName]) {
                recordedTime=Number(progressMap[keyName].progress)
            }
            if(recordedTime!==null){
                player._h5player_lastrecord_=recordedTime;
            }else{
                player._h5player_lastrecord_=null;
            }
            console.log('last record playing', player._h5player_lastrecord_);
            setTimeout(function(){
                h5Player.tips(`Press Shift-R to restore Last Playback: ${tool.formatCT(player._h5player_lastrecord_)}`)

            },1000)



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


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




        },
        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.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.addEventListener('keydown', t.keydownEvent, true)
                    }
                }
            }
            bindTopWindow()
            consoleLog('bindTopWindow passed')

            Store.clearInvalid(1815)


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

        },
        onVideoTriggering: function () {

            // initialize a single video player - h5Player.playerInstance

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


            let pid=player.getAttribute('_h5pPID');

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

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


            if(!player.offsetHeight||!player.offsetWidth) return null;
            let playerBox = player.parentNode
            if(!playerBox) return player;


            while (playerBox && playerBox.nodeType==1 && playerBox.offsetHeight == 0) playerBox = playerBox.parentNode;
            while (playerBox && playerBox.nodeType==1 && playerBox.offsetHeight < player.offsetHeight) playerBox = playerBox.parentNode;
            playerBox = playerBox || player
            if (playerBox && playerBox.nodeType==1) {
                return playerBox;
            }
            return null;

        },
        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 (tips) {
            let t = this;
            let player = t.player()
            let playerBox = this.getPlayerBox(player)
            //console.log('cp',playerBox)
            if(playerBox && playerBox.nodeType==1) {
                if (!tips.parentNode || tips.parentNode !== playerBox) {
                    playerBox.insertBefore(tips, playerBox.firstChild);
                }
            }
        },
        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();
            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');
                else t.tips(-_amount + ' Sec. Backward')
            }

        },
        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) + '%')
        },
        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) {
                window.localStorage.removeItem('_h5_player_user_agent_')
            } else {
                this.setFakeUA(ua)
            }
            debugMsg('ua', navigator.userAgent)
        },
        switchPlayStatus: function () {
            let t = this
            let player = t.player()
            let taskConf = TCC.getTaskConfig()
            if (taskConf.switchPlayStatus) {
                TCC.doTask('switchPlayStatus')
                return
            }
            if (player.paused) {
                if (taskConf.play) {
                    TCC.doTask('play')
                } else {
                    player.play()
                }
            } 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
            let tipsSelector = '.' + (player.getAttribute('_h5player_tips') || t.tipsClassName)
            let tipsDom = player.ownerDocument.querySelector(tipsSelector) || (t.initTips(), player.ownerDocument.querySelector(tipsSelector))
            if (!tipsDom) {
                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){

                    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

                }



            }
        },
        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:.8; transform: translate(-50%,-50%);
}

@keyframes delayHide{
0%, 99% { opacity:0.8; transform: translate(-50%,-50%); }
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: 10px !important;
background: rgba(0,0,0,0) !important;
color:#496CE5 !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')
            tipsDom.setAttribute('class', tcn)
            tipsDom.setAttribute('unselectable', 'on')
            tipsDom.setAttribute('_potTips_','0')
            t.change_playerBox(tipsDom);

            return true;
        },

        fixNonBoxingVideoTipsPosition:function(tipsDom,player){

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

            let customOffset={left: -player.offsetWidth*.5+_offset.left+.5*tipsDom.offsetWidth, top:-player.offsetHeight*.5+_offset.top+.5*tipsDom.offsetHeight};
            //console.log('A00-coffset',customOffset)



            let p=tipsDom.getBoundingClientRect();
            let q=player.getBoundingClientRect();
            let currentPos=[(p.left+p.right)/2,(p.bottom+p.top)/2];
            let targetPos=[(q.left+q.right)/2+customOffset.left,(q.bottom+q.top)/2+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: {
            'enter': 13,
            'shift': 16,
            'ctrl': 17,
            'alt': 18,
            'esc': 27,
            'space': 32,
            'LEFT': 37,
            'UP': 38,
            'RIGHT': 39,
            'DOWN': 40,
            '1': 49,
            '2': 50,
            '3': 51,
            '4': 52,
            'c': 67,
            'd': 68,
            'e': 69,
            'f': 70,
            'i': 73,
            'j': 74,
            'k': 75,
            'n': 78,
            'o': 79,
            'p': 80,
            'q': 81,
            'r': 82,
            's': 83,
            't': 84,
            'u': 85,
            'w': 87,
            'x': 88,
            'y': 89,
            'z': 90,
            'pad1': 97,
            'pad2': 98,
            'pad3': 99,
            'pad4': 100,
            '\\': 220,
        },
        zoom_keys: ['x', 'c', 'z', 'arrowright', 'arrowleft', 'arrowup', 'arrowdown'],
        /* 播放器事件響應器 */
        playerTrigger: function (player, event) {
            if (!player || !event) return
            let t = h5Player
            let keyCode = event.keyCode
            if (event.code == "Space" && keyCode > 128) keyCode = 32;

            //shift + key
            if (event.shiftKey && !event.ctrlKey && !event.altKey) {
                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)
                    // 阻止事件冒泡
                    event.stopPropagation()
                    event.preventDefault()
                    return true
                }
            }
            // 防止其它無關組合鍵衝突
            if (!event.altKey && !event.ctrlKey && !event.shiftKey) {
                let kControl = null
                consoleLog('keycode', keyCode)
                let newPBR, oldPBR;
                switch (keyCode) {
                        // 方向鍵右→:快進3秒
                    case 39:
                        t.tuneCurrentTime(t.skipStep)
                        break;
                        // 方向鍵左←:後退3秒
                    case 37:
                        t.tuneCurrentTime(-t.skipStep)
                        break;
                        // 方向鍵上↑:音量升高 1%
                    case 38:
                        t.tuneVolume(0.01)
                        break;
                        // 方向鍵下↓:音量降低 1%
                    case 40:
                        t.tuneVolume(-0.01)
                        break;
                        // 空格鍵:暫停/播放
                    case h5Player.keyMap.space:
                        t.switchPlayStatus()
                        break;
                        // 按鍵X:減速播放 -0.1
                    case h5Player.keyMap.x:
                        if (player.playbackRate > 0) {
                            t.setPlaybackRate(player.playbackRate - 0.1)
                        }
                        break;
                        // 按鍵C:加速播放 +0.1
                    case h5Player.keyMap.c:
                        if (player.playbackRate < 16) {
                            t.setPlaybackRate(player.playbackRate + 0.1)
                        }
                        break;
                        // 按鍵Z:正常速度播放
                    case h5Player.keyMap.z:
                        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的意思 */
                        if (!player.paused) player.pause()
                        player.currentTime += Number(1 / t.fps)
                        t.tips('Jump to: Next frame')
                        break;
                        // 按鍵D:上一幀
                    case h5Player.keyMap.d:
                        if (!player.paused) player.pause()
                        player.currentTime -= Number(1 / t.fps)
                        t.tips('Jump to: Previous frame')
                        break;
                        // 按鍵E:亮度增加%
                    case h5Player.keyMap.e:
                        kControl = 'brightness'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵W:亮度減少%
                    case h5Player.keyMap.w:
                        kControl = 'brightness'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                            t.filter.setup()
                        }
                        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵T:對比度增加%
                    case h5Player.keyMap.t:
                        kControl = 'contrast'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵R:對比度減少%
                    case h5Player.keyMap.r:
                        kControl = 'contrast'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                            t.filter.setup()
                        }
                        t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵U:飽和度增加%
                    case h5Player.keyMap.u:
                        kControl = 'saturate'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵Y:飽和度減少%
                    case h5Player.keyMap.y:
                        kControl = 'saturate'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                            t.filter.setup()
                        }
                        t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵O:色相增加 1 度
                    case h5Player.keyMap.o:
                        kControl = 'hue-rotate'
                        t.filter.key['hue-rotate'] += 1
                        t.filter.setup()
                        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
                        break;
                        // 按鍵I:色相減少 1 度
                    case h5Player.keyMap.i:
                        kControl = 'hue-rotate'
                        t.filter.key['hue-rotate'] -= 1
                        t.filter.setup()
                        t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
                        break;
                        // 按鍵K:模糊增加 0.1 px
                    case h5Player.keyMap.k:
                        kControl = 'blur'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = (+t.filter.key[kControl] || 0).toFixed(1)
                        t.filter.setup()
                        t.tips('Blur: ' + t.filter.key[kControl] + ' px')
                        break;
                        // 按鍵J:模糊減少 0.1 px
                    case h5Player.keyMap.j:
                        kControl = 'blur'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = (+t.filter.key[kControl] || 0).toFixed(1)
                            t.filter.setup()
                        }
                        t.tips('Blur: ' + t.filter.key[kControl] + ' px')
                        break;
                        // 按鍵Q:圖像復位
                    case h5Player.keyMap.q:
                        t.filter.reset()
                        t.tips('Video Filter Reset')
                        break;
                        // 按鍵S:畫面旋轉 90 度
                    case h5Player.keyMap.s:
                        t.rotate += 90
                        if (t.rotate % 360 === 0) t.rotate = 0;
                        player.style.transform = 'rotate(' + t.rotate + 'deg)'
                        t.tips('Rotation:' + t.rotate + ' deg')
                        break;
                        // 按鍵迴車,進入FULLSCREEN
                    case h5Player.keyMap.enter:
                        t.callFullScreenBtn();
                        break;
                    case h5Player.keyMap.n:
                        pictureInPicture(player);
                        break;
                    default:
                        // 按1-4設置播放速度 49-52;97-100
                        if ((keyCode >= 49 && keyCode <= 52) || (keyCode >= 97 && keyCode <= 100)) {
                            player.playbackRate = Number(event.key)
                            t.setPlaybackRate(player.playbackRate)
                        }
                }
                // 阻止事件冒泡
                event.stopPropagation()
                event.preventDefault()
                return true
            }
        },
        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: function (event) {
            let t = h5Player
            let keyCode = event.keyCode
            let key = event.key.toLowerCase()
            let player = t.player()
            if (event.code == "Space" && keyCode > 128) keyCode = 32;
            /* 處於可編輯元素中不執行任何快捷鍵 */
            if (t.isEditableTarget(event.target)) return
            /* shift+f 切換UA偽裝 */
            if (event.shiftKey && keyCode === 70) {
                t.switchFakeUA()
            }
            /* 未用到的按鍵不進行任何事件監聽 */
            //console.log('-keycode-', keyCode)
            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','|']);
            let isInUseCode = t.keyCodeList.includes(keyCode) || t.keyList.includes(key)
            if (!isInUseCode) return
            if (!player) {
                // console.log('無可用的播放,不執行相關操作')
                return
            }
            /* 切換插件的可用狀態 */
            if (event.ctrlKey && keyCode === 32) {
                t.enable = !t.enable;
                if (t.enable) {
                    t.tips('啟用h5Player插件')
                } else {
                    t.tips('禁用h5Player插件')
                }
            }
            if (!t.enable) {
                consoleLog('h5Player 已禁用~')
                return false
            }
            // 按ctrl+\ 鍵進入聚焦或取消聚焦狀態,用於視頻標籤被遮擋的場景
            if (event.ctrlKey && keyCode === 220) {
                t.globalMode = !t.globalMode
                if (t.globalMode) {
                    t.tips('全局模式')
                } else {
                    t.tips('禁用全局模式')
                }
            }
            /* 非全局模式下,不聚焦則不執行快捷鍵的操作 */
            if (!t.globalMode && !t._isFoucs) return
            /* 判斷是否執行了自定義快捷鍵操作,如果是則不再響應後面默認定義操作 */
            if (t.runCustomShortcuts(player, event) === true) return
            /* 響應播放器相關操作 */
            t.playerTrigger(player, event)
        },
        /**
             * 獲取播放進度
             * @param player -可選 對應的h5 播放器對象, 如果不傳,則獲取到的是整個播放進度表,傳則獲取當前播放器的播放進度
             */

        getPlayProgressMap: function (player) {
            let progressMap = Store.read('_play_progress_')
            if (!progressMap) {
                progressMap = {}
            } else {
                progressMap = JSON.parse(progressMap)
            }
            return progressMap
        },
        /* 設置播放進度 */
        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();
            }
        }
    }



    hackAttachShadow()
    hackEventListener()
    gbl.h5Player = h5Player;

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



    }

    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;

    try {

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

})();