[Bilibili] MiniScreenResizer

Change Bilibili video miniscreen with your mouse wheel

// ==UserScript==
// @name      [Bilibili] 小窗尺寸缩放
// @name:en         [Bilibili] MiniScreenResizer
// @name:zh      [Bilibili] 小窗尺寸缩放
// @namespace    ckylin-script-bili-miniscreenresizer
// @version      0.1
// @description  使用滚轮修改视频小窗口尺寸
// @description:en  Change Bilibili video miniscreen with your mouse wheel
// @description:zh  使用滚轮修改视频小窗口尺寸
// @author       CKylinMC
// @match        https://www.bilibili.com/video/*
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license      GPLv3 License
// ==/UserScript==

(function() {
    const $ = q=>document.querySelector(q);
    const $$ = q=>[...document.querySelectorAll(q)];
    const BVC = {
        mini: '.bpx-player-container[data-revision="1"][data-screen=mini], .bpx-player-container[data-revision="2"][data-screen=mini]',
        container: '.bpx-player-container',
    };
    const CACHE = {};
    const setWH = (e,w,h)=>(e.style.width=w+'px',e.style.height=h+'px',[w,h]);
    const parsePX = px=>parseFloat(px.replace('px',''));
    const getWH = e=>{const es = getComputedStyle(e);return [parsePX(es.width),parsePX(es.height)]};
    const setTrans = (e,cancel)=>e.style.transition=cancel?'none':"width .3s ease, height .3s ease";
    const wait = t => new Promise(r => setTimeout(r, t));
    const setWHcss = (selector,w,h)=>css("RESIZERCSS",`${selector}{ width:${w}px!important;height:${h}px!important; }`);

    function css(id,content){
        let el = $('style#'+id);
        if(!el){
            el = document.createElement('style');
            el.id = id;
            document.body.appendChild(el);
        }
        el.innerHTML = content;
        return el;
    }

    function parseFl (fl){
        try{
            const f = parseFloat(fl);
            if(!isNaN(f) && f>=0) return f;
        }catch(e){}
        return -1
    }

    let menuIds = [];
    let menus = {};
    const registerMenu = (text, callback) => menuIds.push(GM_registerMenuCommand(text, callback));
    const clearMenu = () => { menuIds.forEach(id => GM_unregisterMenuCommand(id)); menuIds = []; };

    async function playerReady(){
        let i=50;
        while(--i>=0){
            await wait(200);
            if(!('player' in unsafeWindow)) continue;
            if(!('isInitialized' in unsafeWindow.player)) continue;
            if(!unsafeWindow.player.isInitialized()) continue;
            return true;
        }
        return false;
    }

    async function waitForDom(q) {
        let i = 50;
        let dom;
        while (--i >= 0) {
            if (dom = $(q)) break;
            await wait(100);
        }
        return dom;
    }

    function applyMenus() {
        clearMenu();
        for (let item in menus) {
            if(!menus.hasOwnProperty(item)) continue;
            let menu = menus[item];
            registerMenu(menu.text, menu.callback);
        }
    }

    function setMenu(id,text,callback,noapply = false) {
        menus[id] = { text, callback };
        if (!noapply) applyMenus();
    }

    function resize(w,h){
            const e = $(BVC.mini);
            setTrans(e);
            if(w&&h){
                setWHcss(/*e*/BVC.mini,w,h);
            }
            return getWH(e);
        }

    (unsafeWindow||window).resizer = {
        $,$$,BVC,setWH,parsePX,getWH,setWHcss,
        resize
    }

    function prevent(e){
        if(!CACHE.container) return;
        const el = CACHE.container;
        if(el.getAttribute('data-screen')!='mini'){
            return;
        }
        e.preventDefault();
        e.stopPropagation();
    }

    function onMouseWheel(e){
        if(!CACHE.container) return;
        const el = CACHE.container;
        if(el.getAttribute('data-screen')!='mini'){
            return;
        }
        e.preventDefault();
        e.stopPropagation();
        console.log(e,e.wheelDeltaY);
        let [w,h] = getWH(CACHE.container);
        let delta = e.wheelDeltaY || e.deltaY;
        delta = 0.1*delta;
        w+=delta;
        h = w/16*9;
        setTrans(CACHE.container,true);
        setWHcss(/*CACHE.container*/BVC.mini,w,h);
        return false;
    }

    async function inject(){
        await playerReady();
        console.log('player ready');
        await waitForDom(BVC.container);
        console.log('dom ready');
        const el = $(BVC.container);
        if(!el) {
            throw new Error();
        }
        console.log('element ready');
        CACHE.container = el;
        el.addEventListener('onwheel',e=>prevent(e),false);
        el.addEventListener('mousewheel',e=>onMouseWheel(e),false);
        el.addEventListener('DOMMouseScroll',e=>onMouseWheel(e),false);
    }

    function init(){
        [
            {
                id:'240',
                text: "240x135",
                callback: ()=>resize(240,135)
            },
            {
                id:'320',
                text: "320x180(默认)",
                callback: ()=>resize(320,180)
            },
            {
                id:'480',
                text: "480x270",
                callback: ()=>resize(480,320)
            },
            {
                id:'640',
                text: "640x360",
                callback: ()=>resize(640,360)
            },
            {
                id:'custom',
                text: "自定义",
                callback: ()=>{
                    const curr = getWH($(BVC.mini));
                    const w = parseFl(prompt("宽度:(当前"+curr[0]+")"));
                    if(w<0) return alert("输入不是有效大于0数字,已取消输入");
                    const h = parseFl(prompt("高度:(当前"+curr[1]+",根据输入的宽度("+w+")推荐高度:"+(Math.round(w/16*9))+")"));
                    if(h<0) return alert("输入不是有效大于0数字,已取消输入");
                    setTimeout(()=>resize(w,h),200);
                }
            },
        ].forEach(it=>setMenu(it.id,it.text,it.callback,true));
        applyMenus();
        inject().then(()=>console.log('[RESIZER] injected')).catch(e=>console.error('[RESIZER] cannot inject'));
    }
    init();
})();