[Bilibili] 视频旋转

旋转和缩放视频,防止某些视频伤害到你的脖子或眼睛!

Установить этот скрипт?
Рекомендуемый автором скрипт

Вам также может понравится [Bilibili] 关注管理器.

Установить этот скрипт
// ==UserScript==
// @name         [Bilibili] 视频旋转
// @namespace    ckylin-script-bilibili-rotate
// @version      0.15
// @description  旋转和缩放视频,防止某些视频伤害到你的脖子或眼睛!
// @author       CKylinMC
// @match        https://www.bilibili.com/video/*
// @include      https://www.bilibili.com/medialist/play/*
// @include      https://www.bilibili.com/bangumi/play/*
// @include      https://bangumi.bilibili.com/anime/*/play*
// @include      https://bangumi.bilibili.com/movie/*
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_getResourceText
// @grant        unsafeWindow
// @license      GPL-3.0-only
// ==/UserScript==
(function () {
    'use strict';
    let effects = [];
    /*
    * 实验性功能:
    * `injectToVideo`开关决定将面板注入到页面外围还是播放器中。
    *
    * 如果设置为 true 脚本会注入到播放器中,那么你可以在全屏时使用面板,但是由于b站
    * 切换视频会重置播放器,因此脚本将会持续观测播放器以响应变更
    * 此功能可能会导致页面卡顿或其他问题。
    *
    * 如果设置为 false 脚本会注入到页面外围,此时脚本注入完成后不会影响页面功能,
    * 但是如果你进入全屏模式将无法看到侧功能栏,脚本将只能通过快捷键来操作视频旋转缩放。
    *
    * 此选项由脚本自动维护,无需手动修改。
    */
    let injectToVideo = false;
    const loadInjectOption = (val=null) =>{
        if(val!==null){
            GM_setValue('ckrotate_injecttovideo',val);
        }
        let opt = GM_getValue('ckrotate_injecttovideo',false);
        if(opt===null||opt===undefined) opt = false;
        injectToVideo = opt;
    }
    loadInjectOption();
    const wait = (t) => {
        return new Promise(r => setTimeout(r, t));
    }
    const waitForPageVisible = async () => {
        return document.hidden && new Promise(r=>document.addEventListener("visibilitychange",r))
    }

    class EventEmitter {
        handlers = {};

        on(name, func) {
            if (!(func instanceof Function)) throw "Param must be func!";
            if (!(name in this.handlers)) {
                this.handlers[name] = [];
            }
            this.handlers[name].push(func);
        }

        off(name, func) {
            if (!(func instanceof Function)) throw "Param must be func!";
            if (name in this.handlers) {
                for (let i = 0; i < this.handlers[name].length; i++) {
                    if (this.handlers[name][i] === func) {
                        this.handlers[name].splice(i, 1);
                        i--;
                    }
                }
            }
        }

        emit(name, ...args) {
            if (name in this.handlers) {
                for (let func of this.handlers[name]) {
                    try {
                        func(...args);
                    } catch (e) {
                        console.error('ERROR:', e);
                    }
                }
            }
        }
    }

    class HoldClick {
        dom;
        emitter = new EventEmitter;
        downTime = 0;
        holdingTime = 250;
        mouseDown = false;

        constructor(dom, holdingTime = 250) {
            if (dom instanceof HTMLElement) {
                this.dom = dom;
                this.initListener();
            }
            this.holdingTime = holdingTime;
        }

        onclick(func) {
            this.emitter.on("click", func);
            return this;
        }

        onhold(func) {
            this.emitter.on("hold", func);
            return this;
        }

        onup(func) {
            this.emitter.on("up", func);
            return this;
        }

        offclick(func) {
            this.emitter.off("click", func);
            return this;
        }

        offhold(func) {
            this.emitter.off("hold", func);
            return this;
        }

        offup(func) {
            this.emitter.off("up", func);
            return this;
        }

        handleMouseDown(e) {
            e.preventDefault();
            this.mouseDown = true;
            this.downTime = (new Date()).getTime();
            setTimeout(() => {
                if (this.mouseDown) {
                    console.log(this);
                    this.mouseDown = false;
                    this.downTime = 0;
                    this.emitter.emit("hold", e);
                }
            }, this.holdingTime)
        }

        handleMouseUp(e) {
            e.preventDefault();
            if (this.mouseDown) {
                this.mouseDown = false;
                const currTime = (new Date).getTime();
                if ((currTime - this.downTime) >= this.holdingTime) {
                    this.emitter.emit("hold", e);
                } else {
                    this.emitter.emit("click", e);
                }
                this.downTime = 0;
            }
            this.emitter.emit("up", e);
        }

        handleMouseOut(e) {
            e.preventDefault();
            if (this.mouseDown) {
                this.mouseDown = false;
                this.downTime = 0;
                this.emitter.emit("hold", e);
            }
        }

        initListener() {
            this.dom.addEventListener("mouseup", this.handleMouseUp.bind(this))
            this.dom.addEventListener("mousedown", this.handleMouseDown.bind(this))
            this.dom.addEventListener("mouseout", this.handleMouseOut.bind(this))
        }
    }

    const dragger = {
        defaultHandler: (val) => console.log("DRAG:", val),
        waitForDragger: async (waitStatus = true) => {
            while (dragger.dragging !== waitStatus) await wait(10);
            return dragger;
        },
        regHandler: async (func) => {
            if (!(func instanceof Function)) throw "Param must be a func!";
            await dragger.waitForDragger(false);
            dragger.handler = func;
            return dragger;
        },
        handler: () => {
        },
        dragging: false,
        initialDragData: {
            x: 0,
            y: 0
        },
        lastDragData: {
            x: 0,
            y: 0
        },
        startDrag: (e) => {
            if (dragger.dragging) return;
            dragger.dragging = true;
            console.log(dragger.initialDragData);
            dragger.initialDragData.x = e.screenX;
            dragger.initialDragData.y = e.screenY;
            dragger.lastDragData.x = e.screenX;
            dragger.lastDragData.y = e.screenY;
            document.body.addEventListener("mouseup", dragger.stopDrag);
            document.body.addEventListener("mousemove", dragger.handleDrag);
            console.info("DRAG:", "Start Drag");
            return dragger;
        },
        handleDrag: (e) => {
            const currPos = {
                x: e.screenX,
                y: e.screenY
            };
            const initPos = dragger.initialDragData;
            const delta = {
                x: initPos.x - currPos.x,
                y: initPos.y - currPos.y
            }
            const lastdelta = {
                x: dragger.lastDragData.x - currPos.x,
                y: dragger.lastDragData.y - currPos.y
            }
            dragger.lastDragData = currPos;
            dragger.handler(delta, lastdelta);
        },
        stopDrag: () => {
            document.body.removeEventListener("mouseup", dragger.stopDrag);
            document.body.removeEventListener("mousemove", dragger.handleDrag);
            dragger.handler = dragger.defaultHandler;
            console.info("DRAG:", "Stop Drag");
            dragger.dragging = false;
            return dragger;
        },
    }

    async function playerReady() {
        let i = 50;
        while (--i >= 0) {
            await wait(100);
            if (!('player' in unsafeWindow)) continue;
            if (!('isInitialized' in unsafeWindow.player)) continue;
            if (!unsafeWindow.player.isInitialized()) continue;
        }
        if(i<0)return false;
        await waitForPageVisible();
        while(1){
            await wait(200);
            if(document.querySelector(".bilibili-player-video-control-wrap,.bpx-player-video-wrap")) return true;
        }
    }

    function bindKeys() {
        unsafeWindow.addEventListener("keypress", e => {
            if (e.key == "Q") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                leftR();
                e.preventDefault();
            } else if (e.key == "E") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                rightR();
                e.preventDefault();
            } else if (e.key == "A") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                smartLR();
                e.preventDefault();
            } else if (e.key == "D") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTARREA"].includes(e.target.tagName)) return;
                smartRR();
                e.preventDefault();
            } else if (e.key == "R") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                cleanEffects();
                clearStyles();
                e.preventDefault();
            } else if (e.key == "+") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                zoomIn();
                e.preventDefault();
            } else if (e.key == "-") {
                if (!e.shiftKey) return;
                if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
                zoomOut();
                e.preventDefault();
            }
        });
    }

    function addEffects(name, value, wait = false) {
        let effect = effects.filter(e => e.name == name);
        if (effect.length) {
            effect[0].value += value;
        } else {
            effects.push({name, value});
        }
        if (!wait) applyEffects();
    }

    function setEffects(name, value, wait = false) {
        delEffect(name, true);
        addEffects(name, value, wait);
    }

    function delEffect(name, wait = false) {
        effects.forEach((e, i) => {
            if (e.name == name)
                effects.splice(i, 1);
        })
        if (!wait) applyEffects();
    }

    function applyEffects() {
        let style = ".bilibili-player-video video,.bilibili-player-video bwp-video,.bpx-player-video-wrap video { transform: ";
        effects.forEach(e => {
            let key = e.name;
            let value = e.value + "";
            switch (key) {
                case "rotate":
                    value += "deg";
                    break;
                case "translateY":
                case "translateX":
                    value += "px";
                    break;
                case "scale":
                    value = (1 - e.value) + "";
                    break;
            }
            style += ` ${key}(${value})`;
        });
        style += "}";
        console.log(style);
        clearStyles();
        addStyle(style);
    }

    function cleanEffects() {
        effects = [];
    }

    function clearStyles(className = "CKROTATE") {
        let dom = document.querySelectorAll("style." + className);
        if (dom) [...dom].forEach(e => e.remove());
    }

    function addStyle(s, className = "CKROTATE") {
        if (className !== "CKROTATE") clearStyles(className);
        let style = document.createElement("style");
        style.classList.add(className);
        style.innerHTML = s;
        document.body.appendChild(style);
    }

    function setR(val = 0) {
        setEffects("rotate", val);
    }

    function leftR(val = 90) {
        addEffects("rotate", val * -1);
    }

    function rightR(val = 90) {
        //debug
        //alert("rightR");
        addEffects("rotate", val);
    }

    function upR() {
        addEffects("rotate", 180);
    }

    function cR() {
        delEffect("rotate");
    }

    function setZ(val = 0) {
        setEffects("scale", val);
    }

    function zoomIn(val = 0.1) {
        addEffects("scale", val * -1);
    }

    function zoomOut(val = 0.1) {
        addEffects("scale", val);
    }

    function cZ() {
        delEffect("scale");
    }

    function setMY(val = 0) {
        setEffects("translateY", val);
    }

    function moveUp(val = 10) {
        addEffects("translateY", val * -1);
    }

    function moveDown(val = 10) {
        addEffects("translateY", val);
    }

    function setMX(val = 0) {
        setEffects("translateX", val);
    }

    function setPos(x, y) {
        setEffects("translateX", x, true);
        setEffects("translateY", y);
    }

    function movePos(x, y) {
        addEffects("translateX", x, true);
        addEffects("translateY", y);
    }

    function moveLeft(val = 10) {
        addEffects("translateX", val * -1);
    }

    function moveRight(val = 10) {
        addEffects("translateX", val);
    }

    function cM() {
        delEffect("translateX");
        delEffect("translateY");
    }

    function smartLR() {
        let dom = document.querySelector(".bilibili-player-video video,.bilibili-player-video bwp-video,.bpx-player-video-wrap video");
        if (!dom) return;
        let w = dom.videoWidth;
        let h = dom.videoHeight;
        let s = h / w;
        clearStyles();
        cleanEffects();
        addEffects("rotate", -90, true);
        addEffects("scale", 1 - s);
    }

    function smartRR() {
        let dom = document.querySelector(".bilibili-player-video video,.bilibili-player-video bwp-video,.bpx-player-video-wrap video");
        if (!dom) return;
        let w = dom.videoWidth;
        let h = dom.videoHeight;
        let s = h / w;
        clearStyles();
        cleanEffects();
        addEffects("rotate", 90, true);
        addEffects("scale", 1 - s);
    }

    function showTip() {
        addStyle(`
            #CKToast{
                background: white;
                position: fixed;
                top: 80px;
                right: 20px;
                border-radius: 3px;
                border-left: solid 4px #2196f3;
                padding: 10px;
                color: black;
                font-size: large;
                overflow: hidden;
                word-break: all;
                animation: CKToastIn cubic-bezier(0, 0, 0, 1.18) .5s forwards;
            }
            .dark #CKToast{
                background: #424242;
                color: white;
            }

            #CKToast button{
                border: none;
                background: #2196f3;
                color:white;
                padding:3px 6px;
                display: inline-block;
                margin: 3px;
                border-radius: 3px;
                cursor:pointer;
                font-size: medium;
                transition: all .3s;
            }
            #CKToast button:hover{
                filter: brightness(.5);
            }
            .dark #CKToast button{
                background: #1976d2;
            }
            @keyframes CKToastIn{
                from{
                    right: -100%;
                }
            }
            @keyframes CKToastOut{
                to{
                    right: -100%;
                }
            }
        `, "CKToastUIStyles");
        const toast = document.createElement("div");
        toast.id = "CKToast";
        toast.innerHTML = "检测到视频可能需要旋转<br>";
        const left = document.createElement("button");
        left.innerHTML = "左转90°";
        left.onclick = () => {
            smartLR();
            closeTip();
        }
        toast.appendChild(left);
        const right = document.createElement("button");
        right.innerHTML = "右转90°";
        right.onclick = () => {
            smartRR();
            closeTip();
        }
        toast.appendChild(right);
        const close = document.createElement("button");
        close.innerHTML = "关闭";
        close.style.background = "#d81b60";
        close.onclick = () => {
            closeTip();
        }
        toast.appendChild(close);
        document.body.appendChild(toast);
        setTimeout(closeTip, 10000);
    }

    function closeTip() {
        const toast = document.querySelector("#CKToast");
        if (toast) {
            toast.style.animation = null;
            toast.style.animation = "CKToastOut cubic-bezier(0.93, -0.32, 1, 1) .5s forwards";
            setTimeout(() => toast.remove(), 500);
        }
    }

    async function videoDetect() {
        if (!(await playerReady())) return;
        let dom = document.querySelector(".bilibili-player-video video,.bilibili-player-video bwp-video,.bpx-player-video-wrap video");
        if (!dom) return;
        let w = dom.videoWidth;
        let h = dom.videoHeight;
        if (h > w) {
            showTip();
        }
    }

    GM_registerMenuCommand("左转90", () => {
        leftR();
    });

    GM_registerMenuCommand("右转90°", () => {
        rightR();
    });

    GM_registerMenuCommand("智能左转90", () => {
        smartLR();
    });

    GM_registerMenuCommand("智能右转90°", () => {
        smartRR();
    });

    GM_registerMenuCommand("180°", () => {
        upR();
    });

    GM_registerMenuCommand("放大", () => {
        zoomIn();
    });

    GM_registerMenuCommand("缩小°", () => {
        zoomOut();
    });

    GM_registerMenuCommand("向上", () => {
        moveUp();
    });

    GM_registerMenuCommand("向下", () => {
        moveDown();
    });

    GM_registerMenuCommand("向左", () => {
        moveLeft();
    });

    GM_registerMenuCommand("向右", () => {
        moveRight();
    });

    GM_registerMenuCommand("清除旋转", () => {
        cR();
    });

    GM_registerMenuCommand("清除缩放", () => {
        cZ();
    });

    GM_registerMenuCommand("清除位移", () => {
        cM();
    });

    GM_registerMenuCommand("重置", () => {
        cleanEffects();
        clearStyles();
    });

    GM_registerMenuCommand("注入播放器", () => {
        if(confirm(`注入播放器功能(实验性)
    * 当前状态:${injectToVideo?"开":"关"}
    * 点击"确认"修改为 ${injectToVideo?"关":"开"} ,点击取消不做修改。
    * 
    * 说明
    * 
    * 此开关决定将面板注入到页面外围还是播放器中。
    *
    * 如果设置为 开 脚本会注入到播放器中,那么你可以在全屏时使用面板,但是由于b站
    * 切换视频会重置播放器,因此脚本将会持续观测播放器以响应变更。
    * 此功能可能会导致页面卡顿或其他问题(欢迎反馈遇到的问题)。
    *
    * 如果设置为 关 脚本会注入到页面外围,此时脚本注入完成后不会影响页面功能,
    * 但是如果你进入全屏模式将无法看到侧功能栏,脚本将只能通过快捷键来操作视频旋转缩放。`)){
            loadInjectOption(!injectToVideo);
            alert(`已修改为 ${injectToVideo?"开":"关"} 刷新页面生效`);
        }
    });

    /* Thanks for yoringboy's contributings! */
    function makeButton(icon, contents, color) {
        let ico = document.createElement("i");
        ico.classList.add("mdi", "mdi-18px", "mdi-" + icon);
        if (color) ico.style.color = color;
        let btn = document.createElement("div");
        btn.classList.add("ckrotate-btn");
        // btn.innerHTML = contents;
        btn.appendChild(ico);
        btn.setAttribute("data-btnname", contents);
        return btn
    }

    let lastObTimeout = null;
    let ignoreNextObEvent = false;
    let ob = null

    function injectButtons() {
        if(!document.querySelector("#mdiiconcss"))
            document.head.innerHTML += `<link id="mdiiconcss" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.min.css"/>`
        addStyle(`
        #ckrotate-hidden-btn{
            z-index: 9999;
            position: fixed;
            left: -15px;
            width: 30px;
            height: 30px;
            background: black;
            opacity: 0.1;
            color: white;
            cursor: pointer;
            border-radius: 50%;
            text-align: right;
            line-height: 30px;
            transition: opacity .3s 1s, background .3s, color .3s;
            top: 120px;
            top: 30vh;
        }
        #ckrotate-hidden-btn:hover{
            transition: opacity .3s 0s, background .3s, color .3s;
            background: white;
            color: black;
            opacity: 0.75;
        }
        #ckrotate-hidden-btn.hide{
            left: -40px;
        }
        #ckrotate-btn-base{
            z-index: 9999;
            position: fixed;
            top: 55px;
            left: 20px;
            width: 110px;
            height: 450px;
            opacity: 0.75;
            background: black;
            color: white;
            text-align: center;
            cursor: pointer;
            flex: 1;
            border-radius: 8px;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            flex-wrap: wrap;
            align-content: stretch;
            justify-content: space-between;
            align-items: center;
            max-height: 90vh;
            transition: all .3s;
        }
        #ckrotate-btn-base.hide{
            left: -120px;
            opacity: 0;
        }
        #ckrotate-btn-base .ckrotate-btn{
            display: flex;
            width: 55px;
            flex-flow: column;
            min-height: 55px;
            flex-wrap: nowrap;
            align-content: center;
            justify-content: center;
            align-items: center;
            transition: all .3s;
            border-radius: 8px;
            background: black;
        }
        #ckrotate-btn-base .ckrotate-btn:hover{
            background: white;
            color: transparent;
        }
        #ckrotate-btn-base .ckrotate-btn:hover>*{
            opacity: 0;
        }
        #ckrotate-btn-base .ckrotate-btn:hover::after{
            color: black;
            content: attr(data-btnname);
            transform: translateY(-80%);
        }
        `, "CKRotateBtnsStyle");
        const togglePanel = show => {
            const btn = document.querySelector("#ckrotate-hidden-btn");
            const panel = document.querySelector("#ckrotate-btn-base");
            if (show) {
                btn.className = "hide";
                panel.className = "show";
            } else {
                btn.className = "show";
                panel.className = "hide";
            }
        };
        const toggle = document.createElement("div");
        toggle.id = "ckrotate-hidden-btn";
        toggle.innerHTML = `<i class="mdi mdi-18px mdi-chevron-right"></i>`;
        toggle.onclick = () => togglePanel(true);
        const btnRoot = document.createElement("div");
        btnRoot.id = "ckrotate-btn-base";
        btnRoot.classList.add("hide");
        let toggleBtn = makeButton("chevron-left", "隐藏");
        toggleBtn.onclick = () => togglePanel(false);
        btnRoot.appendChild(toggleBtn);
        let LRBtn = makeButton("rotate-left", "左转90", "orange");
        new HoldClick(LRBtn)
            .onclick(() => {
                leftR();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,false,{x:e.clientX,y:e.clientY},{down:"rotate-right",up:"rotate-left"});
                await (await dragger.regHandler((delta, lastdelta) => leftR(lastdelta.y))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // LRBtn.onclick = function () {
        //     leftR();
        // };
        // LRBtn.oncontextmenu = async function (e){
        //     e.preventDefault();
        //     disableAnim();
        //     await (await dragger.regHandler(delta => {
        //         setR(delta.y*-1);
        //     })).startDrag(e);
        //     enableAnim();
        // };
        btnRoot.appendChild(LRBtn);
        let RRBtn = makeButton("rotate-right", "右转90", "orange");
        new HoldClick(RRBtn)
            .onclick(() => {
                rightR();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,false,{x:e.clientX,y:e.clientY},{up:"rotate-right",down:"rotate-left"});
                await (await dragger.regHandler((delta, lastdelta) => rightR(lastdelta.y))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // RRBtn.onclick = function () {
        //     rightR();
        // };
        // RRBtn.oncontextmenu = async function (e){
        //     e.preventDefault();
        //     disableAnim();
        //     await (await dragger.regHandler(delta => {
        //         setR(delta.y);
        //     })).startDrag(e);
        //     enableAnim();
        // };
        btnRoot.appendChild(RRBtn);
        let RVBtn = makeButton("rotate-3d-variant", "翻转", "orange");
        RVBtn.onclick = function () {
            upR();
        };
        btnRoot.appendChild(RVBtn);
        let SLRBtn = makeButton("undo", "智能左转", "yellow");
        SLRBtn.onclick = function () {
            smartLR();
        };
        btnRoot.appendChild(SLRBtn);
        let SRRBtn = makeButton("redo", "智能右转", "yellow");
        SRRBtn.onclick = function () {
            smartRR();
        };
        btnRoot.appendChild(SRRBtn);
        let ZIBtn = makeButton("arrow-expand-all", "放大", "cadetblue");
        new HoldClick(ZIBtn)
            .onclick(() => {
                zoomIn();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,false,{x:e.clientX,y:e.clientY},{up:"arrow-expand-all",down:"arrow-collapse-all"});
                await (await dragger.regHandler((delta, lastdelta) => zoomIn(lastdelta.y * 0.01))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // ZOBtn.onclick = function () {
        //     zoomIn();
        // };
        btnRoot.appendChild(ZIBtn);
        let ZOBtn = makeButton("arrow-collapse-all", "缩小", "cadetblue");
        new HoldClick(ZOBtn)
            .onclick(() => {
                zoomOut();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,false,{x:e.clientX,y:e.clientY},{down:"arrow-expand-all",up:"arrow-collapse-all"});
                await (await dragger.regHandler((delta, lastdelta) => zoomOut(lastdelta.y * 0.01))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // ZIBtn.onclick = function () {
        //     zoomOut();
        // };
        btnRoot.appendChild(ZOBtn);
        let MUBtn = makeButton("pan-up", "上移", "forestgreen");
        new HoldClick(MUBtn)
            .onclick(() => {
                moveUp();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,true,{x:e.clientX,y:e.clientY});
                await (await dragger.regHandler((delta, lastdelta) => movePos(lastdelta.x*-1, lastdelta.y*-1))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // MUBtn.onclick = function () {
        //     moveUp();
        // };
        btnRoot.appendChild(MUBtn);
        let MDBtn = makeButton("pan-down", "下移", "forestgreen");
        new HoldClick(MDBtn)
            .onclick(() => {
                moveDown();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,true,{x:e.clientX,y:e.clientY});
                await (await dragger.regHandler((delta, lastdelta) => movePos(lastdelta.x*-1, lastdelta.y*-1))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // MDBtn.onclick = function () {
        //     moveDown();
        // };
        btnRoot.appendChild(MDBtn);
        let MLBtn = makeButton("pan-left", "左移", "forestgreen");
        new HoldClick(MLBtn)
            .onclick(() => {
                moveLeft();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,true,{x:e.clientX,y:e.clientY});
                await (await dragger.regHandler((delta, lastdelta) => movePos(lastdelta.x*-1, lastdelta.y*-1))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // MLBtn.onclick = function () {
        //     moveLeft();
        // };
        btnRoot.appendChild(MLBtn);
        let MRBtn = makeButton("pan-right", "右移", "forestgreen");
        new HoldClick(MRBtn)
            .onclick(() => {
                moveRight();
            })
            .onhold(async (e) => {
                disableAnim();
                showArrows(btnRoot,true,{x:e.clientX,y:e.clientY});
                await (await dragger.regHandler((delta, lastdelta) => movePos(lastdelta.x*-1, lastdelta.y*-1))).startDrag(e).waitForDragger(false).then(() => {enableAnim();hideArrows()});
            })
        // MRBtn.onclick = function () {
        //     moveRight();
        // };
        btnRoot.appendChild(MRBtn);
        let CRBtn = makeButton("backup-restore", "清除旋转", "orange");
        CRBtn.onclick = function () {
            cR();
        };
        btnRoot.appendChild(CRBtn);
        let CZBtn = makeButton("magnify-remove-outline", "清除缩放", "cadetblue");
        CZBtn.onclick = function () {
            cZ();
        };
        btnRoot.appendChild(CZBtn);
        let CMBtn = makeButton("pan", "清除位移", "forestgreen");
        CMBtn.onclick = function () {
            cM();
        };
        btnRoot.appendChild(CMBtn);
        let RSBtn = makeButton("close-circle-outline", "重置", "orangered");
        RSBtn.onclick = function () {
            cleanEffects();
            clearStyles();
        };
        btnRoot.appendChild(RSBtn);
        let injectBase;
        ignoreNextObEvent = true;
        if(injectToVideo){
            injectBase = document.querySelector("#bilibiliPlayer") || document.body;
            if(ob===null){
                ob = new MutationObserver(()=>{
                    if(ignoreNextObEvent) return;
                    if(lastObTimeout) clearTimeout(lastObTimeout);
                    setTimeout(()=>{
                        injectButtons();
                        lastObTimeout = null;
                    },300);
                });
            }
            ob.observe(document.querySelector("#bilibiliPlayer"),{childList:true});
        }else{
            injectBase = document.body;
        }
        injectBase.appendChild(toggle);
        injectBase.appendChild(btnRoot);
        ignoreNextObEvent = false;
    }

    function showArrows(domRoot = document.body, horizontal=false, pos = {x:0,y:0},
                        iconlist = {}){
        let icons = {
            icon:iconlist.icon||"hand-right",
            up:iconlist.up||"arrow-up",
            down:iconlist.down||"arrow-down",
            left:iconlist.left||"arrow-left",
            right:iconlist.right||"arrow-right"
        }
        addStyle(`
        #CKROTATE-arrows-base{
            position:fixed;
            pointer-events: none;
            width: 60px;
            height: 60px;
            background: #00a1d6;
            box-shadow: 0px 0px 8px #00a1d6;
            transform: translate(-50%,-50%);
            text-align: center;
            border-radius: 50%;
            border: solid 3px #00a1d6;
        }
        .CKROTATE-arrow-item{
            padding:0;
            margin: 0 auto;
            line-height: 20px;
            /*height: 20px;*/
            color: white;
        }
        `,"CKROTATE-UI-ARROWS");
        hideArrows();
        const baseDom = document.createElement("div");
        baseDom.id="CKROTATE-arrows-base";
        baseDom.style.top = pos.y+"px";
        baseDom.style.left = pos.x+"px";

        const UpArrow = document.createElement("div");
        UpArrow.classList.add("CKROTATE-arrow-item");
        UpArrow.innerHTML = `<i class="mdi mdi-18px mdi-${icons.up}"></i>`;
        baseDom.appendChild(UpArrow);

        const LRArrow = document.createElement("div");
        LRArrow.classList.add("CKROTATE-arrow-item");
        if(horizontal){
            const LeftArrow = document.createElement("div");
            LeftArrow.classList.add("CKROTATE-arrow-item");
            LeftArrow.innerHTML = `<i class="mdi mdi-18px mdi-${icons.left}"></i>`;
            LeftArrow.style.float="left";
            LRArrow.appendChild(LeftArrow);
            const RightArrow = document.createElement("div");
            RightArrow.classList.add("CKROTATE-arrow-item");
            RightArrow.innerHTML = `<i class="mdi mdi-18px mdi-${icons.right}"></i>`;
            RightArrow.style.float="right";
            LRArrow.appendChild(RightArrow);
        }else{
            LRArrow.innerHTML = `<i class="mdi mdi-18px"></i>`;
        }
        baseDom.appendChild(LRArrow);

        const CenterPoint = document.createElement("div");
        CenterPoint.classList.add("CKROTATE-arrow-item");
        CenterPoint.innerHTML = `<i class="mdi mdi-18px mdi-${icons.icon}"></i>`;
        baseDom.appendChild(CenterPoint);

        const DownArrow = document.createElement("div");
        DownArrow.classList.add("CKROTATE-arrow-item");
        DownArrow.innerHTML = `<i class="mdi mdi-18px mdi-${icons.down}"></i>`;
        baseDom.appendChild(DownArrow);

        domRoot.appendChild(baseDom);
    }

    function hideArrows(){
        let olddom = document.querySelector("#CKROTATE-arrows-base");
        if(olddom) olddom.remove();
    }

    function enableAnim() {
        addStyle(".bilibili-player-video video,.bilibili-player-video bwp-video,.bpx-player-video-wrap video{transition: transform cubic-bezier(0.61, 0.01, 0.44, 0.93) .5s;}", "CKANIMATION");
    }

    function disableAnim() {
        clearStyles("CKANIMATION");
    }

    async function startInject() {
        enableAnim();
        bindKeys();
        await videoDetect();
        injectButtons();
    }

    startInject()
        .then(()=>console.log("[CKROTATE]","Inject task completed."))
        .catch(e=>console.error("[CKROTATE]","Inject task failed:",e));
})();