HTML5 Video Player Enhance

To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness),飽和度(saturate),對比度(contrast),速度(playback speed); frame , hue , blur

Від 28.09.2019. Дивіться остання версія.

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

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

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

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

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

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

(У мене вже є менеджер скриптів, дайте мені встановити його!)

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

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

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

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

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

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

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

// ==UserScript==
// @name         HTML5 Video Player Enhance
// @version      2.7.0(tw)
// @description  To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness),飽和度(saturate),對比度(contrast),速度(playback speed); frame , hue , blur
// @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...

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

    let requestAnimationFrame=window.requestAnimationFrame||function(fn) { setTimeout(fn, 3); };

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


    Object.assign(gbl, {
        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;
        },
        gv001: {
            isFull: false,
            isIframe: false,
            autoCheckCount: 0
        },
        h5player_specific_id_rules :{
            'www.acfun.cn': ['.player-container .player'],
            'www.bilibili.com': ['#bilibiliPlayer'],
            'www.douyu.com': ['#js-player-video-case'],
            'www.huya.com': ['#videoContainer'],
            'www.twitch.tv': ['.player'],
            'www.youtube.com': ['#movie_player'],
            'www.yy.com': ['#player'],
            'www.viu.com': ['#viu-player']
        },
        h5player_general_id_rules : ['.dplayer', '.video-js', '.jwplayer'],
    });
    var gv = gbl.gv001;

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



    function consoleLog(){

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

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


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




    var domTool = {
        getRect: function (element) {
            var rect = element.getBoundingClientRect();
            var 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;
            }
        },
        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) {
            var style = document.createElement('style');
            style.type = 'text/css';
            var 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) {
            var dom = document.querySelector(selector)
            if (dom) {
                requestAnimationFrame(function () {
                    dom.style.opacity = 0;
                    dom.style.transform='translate(-9999px)';
                    dom=null;
                })
            }
        }
    };

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



            let t= h5Player;
            let player = this

            var pid= player.getAttribute('_h5pPID')||null;

            console.log('v1')
            if(!pid || !player.currentSrc) return;
            console.log('v2')


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

                console.log('v4')
                t.srcList[pid]=player.currentSrc;
                t.videoSrcFound(player);
            }

            console.log('v3')


        },
        player_mouseEnter:function (e) {
            h5Player._isFoucs = true
            h5Player.playerInstance = e.target
            h5Player.onVideoViewing()
        },
        player_mouseLeave: function (e) {
            h5Player._isFoucs = false

        },
        player_syncPlayback:function (event) {
            h5Player.playerInstance = event.target
            h5Player.onVideoViewing()
            /* 同步之前設定的播放速度 */
            h5Player.setPlaybackRate()
        },
        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);

                }
            });
        },

        mouseover_getPlayer: function (e) {
            if (gv.isFull) {
                return;
            }
            gv.mouseoverEl = e.target;
            var hostname = document.location.hostname;
            var players = [];
            for (var i in gbl.h5player_specific_id_rules) {
                if (domTool.matchRule(hostname, i)) {
                    for (let v of gbl.h5player_specific_id_rules[i]) {
                        players = document.querySelectorAll(v);
                        if (players.length > 0) {
                            break;
                        }
                    }
                    break;
                }
            }
            if (players.length == 0) {
                for (let v of gbl.h5player_general_id_rules) {
                    players = document.querySelectorAll(v);
                    if (players.length > 0) {
                        break;
                    }
                }
            }
            if (players.length == 0 && e.target.nodeName != 'VIDEO' && document.querySelectorAll('video').length > 0) {
                var videos = document.querySelectorAll('video');
                for (let v of videos) {
                    var vRect = v.getBoundingClientRect();
                    if (e.clientX >= vRect.x - 2 && e.clientX <= vRect.x + vRect.width + 2 && e.clientY >= vRect.y - 2 && e.clientY <= vRect.y + vRect.height + 2 && v.offsetWidth > 399 && v.offsetHeight > 220) {
                        players = [];
                        players[0] = handle.autoCheck(v);
                        gv.autoCheckCount = 1;
                        break;
                    }
                }
            }
            if (players.length > 0) {
                var path = e.path || e.composedPath();
                for (let v of players) {
                    if (path.indexOf(v) > -1) {
                        gv.player = v;
                        return;
                    }
                }
            }
            switch (e.target.nodeName) {
                case 'VIDEO':
                case 'OBJECT':
                case 'EMBED':
                    if (e.target.offsetWidth > 399 && e.target.offsetHeight > 220) {
                        gv.player = e.target;

                    }
                    break;
                default:
                    0;
            }
        },
        autoCheck: function (v) {
            var tempPlayer, el = v;
            gv.playerChilds = [];
            gv.playerChilds.push(v);
            while (el = el.parentNode) {
                if (Math.abs(v.offsetWidth - el.offsetWidth) < 15 && Math.abs(v.offsetHeight - el.offsetHeight) < 15) {
                    tempPlayer = el;
                    gv.playerChilds.push(el);
                } else {
                    break;
                }
            }
            return tempPlayer;
        },
        win_receiveMsg: async function (e) {
            var 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;
            }
        }
    };
    var 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.setCurrentTime(0.1, true)
                        h5Player.setPlaybackRate(targetSpeed, true)
                        return true
                    }
                    /* 模擬點擊觸發 */
                    if (speedItems.length >= 3 && /(c|x|z)/.test(key)) {
                        let curIndex = 1
                        speedItems.forEach((item, index) => {
                            if (item.classList.contains('txp_current')) {
                                curIndex = index
                            }
                        })
                        let perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0
                        let nextIndex = curIndex + 1 < speedItems.length ? curIndex + 1 : speedItems.length - 1
                        let target = speedItems[1]
                        switch (key) {
                            case 'z':
                                target = speedItems[1]
                                break
                            case 'c':
                                target = speedItems[nextIndex]
                                break
                            case 'x':
                                target = speedItems[perIndex]
                                break
                        }
                        target.click()
                        let speedNum = Number(target.innerHTML.replace('x'))
                        h5Player.setPlaybackRate(speedNum)
                    }
                }
            },
            'init': function (h5Player, taskConf) {
                // 隱藏水印
                domTool.hideDom('.txp-watermark')
            },
            'include':['||v.qq.com^']
        },
        {
            'fullScreen': function (h5Player, taskConf) {
                h5Player.playerInstance.parentNode.querySelector('.vjs-fullscreen-control').click()
            },
            'include':['||pan.baidu.com^']
        }

    ];

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

        return function uCheck(rule, url) {

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

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

            var rule_m1 = rule.match(/^(\|\|)?([^\^]+)(\^)?$/)
            var 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];
            var wq = w.replace(/\//g, '\\/').replace(/\./g, '\\.').replace(/\|/g, '\\|').replace(/\*/g, '.*');
            var rq = new RegExp('^(.*)(' + wq + ")(.?)", 'i')
            var 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則將所有域名下的任務配置都進行格式化
                 */


            var 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) {
            var isDo = false
            if (!taskName) return isDo
            let taskConf =  this.getTaskConfig()
            if (!tool.isObj(taskConf) ) return isDo
            let task = taskConf[taskName]

            if (!task ) return isDo
            console.log('h5player-dotask',taskName)
            var clickDOM = null;
            if (taskName === 'shortcuts') {
                if (tool.isObj(task) && tool.getType(task.callback) === 'function') {
                    task.callback(h5Player, taskConf, data)
                    isDo = true
                }
            } else if (tool.getType(task) === 'function') {
                task(h5Player, taskConf, data)
                isDo = true
            } 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()
                    });
                    isDo = true
                }
            }
            return isDo
        }
    }


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


        if(!wVideo._readyCount && !shadowRoot){
            document.addEventListener('mouseover', handle.mouseover_getPlayer, false);
            window.addEventListener('message', handle.win_receiveMsg, false);
        }

        /**
		 * 元素監聽器
		 * @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

        var addedVideoNodes=[]

        function check() {


            var mutations=arguments[0]||null


            var requireChecking=true;
            var 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){

                var 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++) {
                        var 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)
            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
        var 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._baseLogTime=(+new Date().getTime())*100-1200000000000;

        EVENT._addEventListener = EVENT.addEventListener
        EVENT._removeEventListener = EVENT.removeEventListener
        // hack addEventListener
        EVENT._evtCount=0;
        EVENT.addEventListener = function () {
            let arg = arguments
            let type = arg[0]
            let listener = arg[1]
            this._addEventListener.apply(this, arg)
            this._listeners = this._listeners || {}
            this._listeners[type] = this._listeners[type] || new Listeners(this,type)
            var addedTime=+new Date().getTime();
            var uid = 100000+(++EVENT._evtCount);
            let listenerObj = {
                //target: this,
                //type,
                listener,
                options: arg[2],
                uid: uid,
                //addedTime:addedTime
            }
            this._listeners[type].hashList[uid+'']=listenerObj;
            this._listeners[type].listenersCount++;
        }
        // hack removeEventListener
        EVENT.removeEventListener = function () {
            let arg = arguments
            let type = arg[0]
            let listener = arg[1]
            this._removeEventListener.apply(this, arg)
            if(this._listeners&&this._listeners[type]&&this._listeners[type].hashList){
                var hashList=this._listeners[type].hashList
                for(var k in hashList){
                    if(hashList[k].listener===listener){
                        delete hashList[k];
                        this._listeners[type].listenersCount--;
                        break;
                    }
                }
            }
        }
    }



    Object.assign(tool,{
        quickSort  : function (arr) {

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

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

            return _quickSort(arr)
        },


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

            function findPlayer(context) {
                context.querySelectorAll('video').forEach(function (player) {
                    list.push(player)
                })
            }
            findPlayer(document)
            // 被封裝在 shadow dom 裡面的video
            if (window._shadowDomList_) {
                window._shadowDomList_.forEach(function (shadowRoot) {
                    findPlayer(shadowRoot)
                })
            }
            return list
        },
        getPlayerWrapDom: function () {
            let t = this
            let player = t.player()
            if (!player) return
            let wrapDom = null
            let playerBox = player.getBoundingClientRect()
            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){
            let t= h5Player;
            var pid= player.getAttribute('_h5pPID')||null;
            if(!pid || !player.currentSrc) return;
            if(player._videoSrcFound)return;
            player._videoSrcFound=true;


            player._isThisPausedBefore_=false;














            player.setAttribute('_h5pUID_', location.hostname.replace('www.','')+'!'+ location.pathname+player.videoWidth + 'x' + player.videoHeight +'l' + (player.duration<<0))




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

            let keyName = player.getAttribute('_h5pUID_')
            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_);



            player._recordingProgress=false;











            /* 增加通用FULLSCREEN,網頁FULLSCREEN-api */
            player.addEventListener('canplay', function () {
                h5Player.initAutoPlay(this)
            })


            player.addEventListener('playing',function(){

                consoleLog('playing')
                if(this._isThisPausedBefore_)
                    requestAnimationFrame(()=>h5Player.tips('Playback resumed',undefined,500))


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

                if (this._recordingProgress == false) {
                    t.playProgressRecorderInit(player)
                    this._recordingProgress = true;
                }

            })

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

            })




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

            try{
                let sVersion=1814;
                if(+window.localStorage.getItem('_h5_player_sVersion_')<sVersion){
                    Object.keys(localStorage).filter(s=>s.indexOf('_h5_player_')===0).forEach(key=>window.localStorage.removeItem(key))
                    window.localStorage.setItem('_h5_player_sVersion_',sVersion)
                }
            }catch(e){}


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

        },
        onVideoViewing: function () {

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


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

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

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


            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.initTips()
            t.playbackRate = t.getPlaybackRate()
            t.isFoucs()


            /* 進行自定義初始化操作 */
            let taskConf = TCC.getTaskConfig()
            if (taskConf.init) {
                TCC.doTask('init', player)
            }
        },
        getPlaybackRate: function () {
            let t = this
            let playbackRate = window.localStorage.getItem('_h5_player_playback_rate_') || t.playbackRate
            return Number(Number(playbackRate).toFixed(1))
        },
        change_playerBox: function (tips) {
            let t = this;
            let player = t.player()
            var playerBox = player.parentNode
            while (playerBox && playerBox.offsetHeight == 0) playerBox = playerBox.parentNode;
            while (playerBox && playerBox.offsetHeight < player.offsetHeight) playerBox = playerBox.parentNode;
            playerBox = playerBox || player.parentNode
            if (playerBox) {
                if (!tips.parentNode || tips.parentNode !== playerBox) {
                    if(playerBox.nodeType==1)
                        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();

            var box1 =  player;
            var box2 = tipsDom;

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

            var playerBox=null;

            box1 = (box1.contains(player) && box1.contains(tipsDom))?box1:null;
            box2 = (box2.contains(player) && box2.contains(tipsDom))?box2:null;

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




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

            let chFull = gbl.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 playerBox = player.ownerDocument.querySelector('.' + tcn)
            if (!playerBox) return this.callFullScreenBtn0();
            let chFull = gbl.checkFullScreen(player.ownerDocument);
            if (chFull === null) return (console.log('chFull', 'null'), this.callFullScreenBtn0());
            if (chFull === true) {
                consoleLog('chFull', 'true')
                player.ownerDocument.exitFullscreen();
                //  var el = [t.player_focus_input]; if (el) {el = el[0].click()}
            } else {
                consoleLog('chFull', 'false')
                var pPlayer = player
                var qPlayer = playerBox
                var clicked = false;
                var _gs1_tmp={};
                var _gs1_filter=function (elq) {
                    return _gs1_tmp.elm == elq.elm ? false : _gs1_tmp.elm.contains(elq.elm)
                };
                var _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 (var q3 = 0; q3 < 4; q3++) { //max 4 layers
                    if (!qPlayer) break;
                    var fs1 = qPlayer.querySelectorAll('[class*="fullscreen"]')
                    if (fs1.length > 0) {
                        // -- indiv-elm --
                        var 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) {
                                var elm = elp.elm;
                                elp.click = elm._listeners && elm._listeners.click && elm._listeners.click.funcCount > 0
                            })
                        }
                        if ('childElementCount' in player) {
                            gs1.forEach(function (elp) {
                                var elm = elp.elm;
                                elp.childElementCount = elm.childElementCount;
                            })
                        }
                        if ('getBoundingClientRect' in player) {
                            gs1.forEach(function (elp) {
                                var elm = elp.elm;
                                var 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)
                        }
                        var gs2 = gs1.filter(function (elp) {
                            return !elp.contains && elp.visible
                        })
                        console.log('fullscreen btn',gs2)
                        //console.log('gs2',gs2)
                        if (gs2.length >= 1) {
                            var gs2_a = gs2.map(elp => elp.elm.className.length)
                            var gs2_b = Math.min.apply(Math, gs2_a)
                            var 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, notips) {
            let taskConf = TCC.getTaskConfig()
            if (taskConf.playbackRate) {
                TCC.doTask('playbackRate')
                return
            }
            let t = this
            let player = t.player()
            let curPlaybackRate
            if (num) {
                num = Number(num)
                if (Number.isNaN(num)) {
                    console.error('h5player: 播放速度轉換出錯')
                    return false
                }
                if (num <= 0) {
                    num = 0.1
                }
                num = Number(num.toFixed(1))
                curPlaybackRate = num
            } else {
                curPlaybackRate = t.getPlaybackRate()
            }
            /* 記錄播放速度的信息 */
            window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate)
            t.playbackRate = curPlaybackRate
            player.playbackRate = curPlaybackRate
            /* 本身處於1被播放速度的時候不再提示 */
            if (!num && curPlaybackRate === 1) return;
            !notips && t.tips('Playback speed: ' + player.playbackRate + 'x')
        },
        /**
		 * 初始化自動播放邏輯
		 * 必須是配置了自動播放按鈕選擇器得的才會進行自動播放
		 */
        initAutoPlay: function (p) {
            let t = this
            let player = p || t.player()
            // 在輪詢重試的時候,如果實例變了,或處於隱藏頁面中則不進行自動播放操作
            if (!player || (p && p !== t.player()) || document.hidden) return
            let taskConf = TCC.getTaskConfig()
            if (player && taskConf.autoPlay && player.paused) {
                TCC.doTask('autoPlay')
                if (player.paused) {
                    // 輪詢重試
                    if (!player._initAutoPlayCount_) {
                        player._initAutoPlayCount_ = 1
                    }
                    player._initAutoPlayCount_ += 1
                    if (player._initAutoPlayCount_ >= 10) {
                        return false
                    }
                    setTimeout(function () {
                        t.initAutoPlay(player)
                    }, 200)
                }
            }
        },
        setCurrentTime: function (num, notips) {
            if (!num) return
            num = Number(num);
            let _num = Math.abs(Number(num.toFixed(1)));
            let t = this;
            let player = t.player();
            let taskConf = TCC.getTaskConfig();
            if (taskConf.currentTime) {
                TCC.doTask('currentTime');
                return
            }
            if (num > 0) {
                if (taskConf.addCurrentTime) {
                    TCC.doTask('addCurrentTime')
                } else {
                    player.currentTime += _num;
                    !notips && t.tips(_num + ' Sec. Forward')
                }
            } else {
                if (taskConf.subtractCurrentTime) {
                    TCC.doTask('subtractCurrentTime')
                } else {
                    player.currentTime -= _num;
                    !notips && t.tips(_num + ' Sec. Backward')
                }
            }
        },
        setVolume: function (num) {
            if (!num) return
            num = Number(num)
            let _num = Math.abs(Number(num.toFixed(2)))
            let t = this
            let player = t.player()
            if (num > 0) {
                if (player.volume < 1) {
                    player.volume += _num
                }
            } else {
                if (player.volume > 0) {
                    player.volume -= _num
                }
            }
            t.tips('Volume: ' + parseInt(player.volume * 100) + '%')
        },
        setFakeUA(ua) {
            ua = ua || UA.userAgentMap.iPhone.safari
            /* 記錄設定的ua信息 */
            window.localStorage.setItem('_h5_player_user_agent_', ua)
            try{
                Object.defineProperty(navigator, 'userAgent', {
                    value: ua,
                    writable: false,
                    configurable: false,
                    enumerable: true
                });
            }catch(e){}
        },
        /* ua偽裝切換開關 */
        switchFakeUA(ua) {
            let customUA = window.localStorage.getItem('_h5_player_user_agent_')
            if (customUA) {
                window.localStorage.removeItem('_h5_player_user_agent_')
            } else {
                this.setFakeUA(ua)
            }
            debugMsg('ua', navigator.userAgent)
        },
        switchPlayStatus: function () {
            let t = this
            let player = t.player()
            let taskConf = TCC.getTaskConfig()
            if (taskConf.switchPlayStatus) {
                TCC.doTask('switchPlayStatus')
                return
            }
            if (player.paused) {
                if (taskConf.play) {
                    TCC.doTask('play')
                } else {
                    player.play()
                }
            } 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;

                var 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
            var tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName + '_' + (+new Date));
            player.setAttribute('_h5player_tips', tcn)
            if (player.ownerDocument.querySelector('.' + tcn)) return


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

@keyframes delayHide{
0% { opacity:1; transform: translate(-50%,-50%); }
99% { opacity:1; transform: translate(-50%,-50%); }
100% { opacity:0; transform:translate(-9999px); }
}
`+`
[_potTips_]{
position: absolute !important;
z-index: 999 !important;
font-size: ${t.fontSize || 16}px !important;
padding: 10px !important;
background: rgba(0,0,0,0.4) !important;
color:white !important;
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);
        },

        fixNonBoxingVideoTipsPosition:function(tipsDom,player){
            var p=tipsDom.getBoundingClientRect();
            var q=player.getBoundingClientRect();
            var pc=[(p.left+p.right)/2,(p.bottom+p.top)/2];
            var qc=[(q.left+q.right)/2,(q.bottom+q.top)/2];

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




            var z1=-(pc[0]-qc[0]);
            var z2=-(pc[1]-qc[1]);
            if(z1 || z2){


                console.log('A01',(p.left+p.right)/2,(p.bottom+p.top)/2,mL);
                console.log('A02',(q.left+q.right)/2,(q.bottom+q.top)/2,mT);
                var y1=z1+mL;
                var y2=z2+mT;

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

        rotate: 0,
        fps: 30,
        /* 濾鏡效果 */
        filter: {
            key: {},
            view_units: {
                'hue-rotate': 'deg',
                'blur': 'px'
            },
            setup: function (options) {
                var view = ''
                for (var view_key in this.key) {
                    var view_unit = this.view_units[view_key] || ''
                    view += view_key + '(' + (+this.key[view_key] || 0).toFixed(3) + view_unit + ') '
                    this.key[view_key] = Number(+this.key[view_key] || 0)
                }
                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,
        /* 播放器的聚焦事件 */
        isFoucs: function () {
            let t = h5Player
            let player = t.player()
            if (!player) return
        },
        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) {
                var kControl = null
                //consoleLog('keycode', keyCode)
                switch (keyCode) {
                        // 方向鍵右→:快進3秒
                    case 39:
                        t.setCurrentTime(t.skipStep)
                        break;
                        // 方向鍵左←:後退3秒
                    case 37:
                        t.setCurrentTime(-t.skipStep)
                        break;
                        // 方向鍵上↑:音量升高 1%
                    case 38:
                        t.setVolume(0.01)
                        break;
                        // 方向鍵下↓:音量降低 1%
                    case 40:
                        t.setVolume(-0.01)
                        break;
                        // 空格鍵:暫停/播放
                    case h5Player.keyMap.space:
                        t.switchPlayStatus()
                        break;
                        // 按鍵X:減速播放 -0.1
                    case h5Player.keyMap.x:
                        if (player.playbackRate > 0) {
                            t.setPlaybackRate(player.playbackRate - 0.1)
                        }
                        break;
                        // 按鍵C:加速播放 +0.1
                    case h5Player.keyMap.c:
                        if (player.playbackRate < 16) {
                            t.setPlaybackRate(player.playbackRate + 0.1)
                        }
                        break;
                        // 按鍵Z:正常速度播放
                    case h5Player.keyMap.z:
                        player.playbackRate = 1
                        t.setPlaybackRate(player.playbackRate)
                        break;
                        // 按鍵F:下一幀
                    case h5Player.keyMap.f:
                        if (window.location.hostname === 'www.netflix.com') return /* netflix 的F鍵是FULLSCREEN的意思 */
                        if (!player.paused) player.pause()
                        player.currentTime += Number(1 / t.fps)
                        t.tips('Jump to: Next frame')
                        break;
                        // 按鍵D:上一幀
                    case h5Player.keyMap.d:
                        if (!player.paused) player.pause()
                        player.currentTime -= Number(1 / t.fps)
                        t.tips('Jump to: Previous frame')
                        break;
                        // 按鍵E:亮度增加%
                    case h5Player.keyMap.e:
                        kControl = 'brightness'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵W:亮度減少%
                    case h5Player.keyMap.w:
                        kControl = 'brightness'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                            t.filter.setup()
                        }
                        t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵T:對比度增加%
                    case h5Player.keyMap.t:
                        kControl = 'contrast'
                        t.filter.key[kControl] += 0.1
                        t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
                        t.filter.setup()
                        t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
                        break;
                        // 按鍵R:對比度減少%
                    case h5Player.keyMap.r:
                        kControl = 'contrast'
                        if (t.filter.key[kControl] > 0) {
                            t.filter.key[kControl] -= 0.1
                            t.filter.key[kControl] = t.filter.key[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) {
            //console.log('-full', gbl.checkFullScreen(document))
            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) {
            var progressMap = window.localStorage.getItem('_h5_player_play_progress_')
            if (!progressMap) {
                progressMap = {}
            } else {
                progressMap = JSON.parse(progressMap)
            }
            return progressMap
        },
        /* 播放進度記錄器 */
        playProgressRecorderInit: function (player) {
            let t = h5Player
            var _player = player;
            var recorder_handle = {

                _timerFunc:function () {
                    var player = _player
                    let progressMap = t.getPlayProgressMap()
                    let keyName = player.getAttribute('_h5pUID_')||''
                    if(!keyName)return;
                    let list = Object.keys(progressMap)
                    /* 只保存最近10個視頻的播放進度 */
                    if (list.length > 10) {
                        /* 根據更新的時間戳,取出最早添加播放進度的記錄項 */
                        let timeList = []
                        list.forEach(function (keyName) {
                            progressMap[keyName] && progressMap[keyName].t && timeList.push(progressMap[keyName].t)
                        })
                        timeList = tool.quickSort(timeList)
                        let timestamp = timeList[0]
                        /* 刪除最早添加的記錄項 */
                        list.forEach(function (keyName) {
                            if (progressMap[keyName].t === timestamp) {
                                delete progressMap[keyName]
                            }
                        })
                    }
                    /* 記錄當前播放進度 */
                    progressMap[keyName] = {
                        progress: player.currentTime,
                        t: new Date().getTime()
                    }
                    /* 存儲播放進度表 */
                    window.localStorage.setItem('_h5_player_play_progress_', JSON.stringify(progressMap))
                    /* 循環偵聽 */
                    recorder_handle._recorder()
                },_recorder : function () {
                    setTimeout(recorder_handle._timerFunc, 1000 * 2)
                }

            };
            recorder_handle._recorder()
        },
        /* 設置播放進度 */
        setPlayProgress: function (player, curTime) {
            if (!player) return
            let t = h5Player
            if (!curTime || Number.isNaN(curTime)) return
            player.currentTime = curTime
            if (curTime > 3) {
                t.tips('- Playback Progress is restored -')
            }
        },
        /**
		 * 檢測h5播放器是否存在
		 * @param callback
		 */

        load: false
    }



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

    function videoDOMFound(video){




        var pdd = document.createElement('div')
        pdd.id = 'mysvg'
        pdd.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>
`;

        if(video.parentNode) video.parentNode.appendChild(pdd);else document.documentElement.appendChild(pdd);




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

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


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



    }

    function wVideo(video) {

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

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

        var 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)
        var pid=video.getAttribute('_h5pPID');


        videoDOMFound(video)






    }


    wVideo._readyCount=0;

    try {

        /* 檢測到有視頻標籤就進行初始化 */
        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)
    }

})();