video speed

try to take over the world!

当前为 2024-01-10 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         video speed
// @namespace    http://tampermonkey.net/
// @version      20240111.2
// @description  try to take over the world!
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

class KeepVideoTime {
    constructor(props) {
        this.key = "_video_cache_";
        this.obj = this.get() || {};
        this.id = null;
    }
    set(key, time) {
        this.obj[key] = time;
        this.store();
    }
    store() {
        localStorage.setItem(this.key, JSON.stringify(this.obj));
    }

    get() {
        let data = localStorage.getItem(this.key);
        if (!data) {
            return null;
        }
        try {
            data = JSON.parse(data)
        } catch (e) {
            return null;
        } finally {
            return data;
        }
    }
    keep(getUrlId, video) {
        if (!this.id) {
            this.id = setInterval(() => {
                let vid = getUrlId();
                if (vid) {
                    this.set(vid, video.currentTime);
                }
            }, 500);
        }
    }
    stop() {
        if (this.id) {
            clearInterval(this.id);
            this.id = null;
        }
    }
}
let KeepVideoTimeInstance = null;
let onceMap = {};
let clickDomOnce = function (selector, key) {
    if (key in onceMap) {
        return;
    }
    onceMap[key] = true;
    let id = setInterval(() => {
        let d = selector();
        if (d.length) {
            d[0].click();
            clearInterval(id);
            delete onceMap[key];
        }
    }, 500);
}
let DefaultAction = {
    "bilibili.com"(video) {
        return [
            {
                group: "屏幕操作",
                name: "宽屏",
                run: false,
                action() {
                    clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-web-enter'), "bpx-player-ctrl-web-enter");
                }
            },
            {
                group: "屏幕操作",
                name: "全屏",
                run: false,
                action() {
                    clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-full'), "bpx-player-ctrl-full");
                }
            },
            {
                group: "屏幕操作",
                name: "undo",
                run: true,
                action() {
                    console.log("undo");
                }
            }
        ]
    },
    'youtube.com'(videoInstance) {
        return [
            {
                group: "屏幕操作",
                name: "宽屏",
                run: false,
                action() {
                    let video = videoInstance.getVideo();
                    if (video.width < window.innerWidth * .8) {
                        clickDomOnce(() => document.getElementsByClassName('ytp-size-button'), "ytp-size-button");
                    }
                }
            },
            {
                group: "屏幕操作",
                name: "全屏",
                run: false,
                action() {
                    clickDomOnce(() => document.getElementsByClassName('ytp-fullscreen-button'), "ytp-fullscreen-button");
                }
            },
            {
                group: "屏幕操作",
                name: "undo",
                run: true,
                action() {
                    console.log("undo");
                }
            },
            {
                name: "记住最后播放时间",
                action(run, cache) {
                    let video = videoInstance.getVideo();
                    let getVid = () => {
                        let vid = location.search.split('?')[1].split('&').map(_=>_.split('=')).filter(_=>_[0]==='v');
                        if (vid.length === 1) {
                            vid = vid[0][1];
                        }
                        return vid;
                    }
                    if (!KeepVideoTimeInstance) {
                        KeepVideoTimeInstance = new KeepVideoTime();
                        let vid = getVid();
                        if (vid && vid in KeepVideoTimeInstance.obj) {
                            if (video.currentTime < KeepVideoTimeInstance.obj[vid]) {
                                video.currentTime = KeepVideoTimeInstance.obj[vid];
                            }
                        }
                    }
                    if (run) {
                        KeepVideoTimeInstance.keep(getVid, video);
                    } else {
                        KeepVideoTimeInstance.stop();
                    }
                },
                run: true,
                replay: true,
            }
        ]
    }
};
function checkWindow() {
    return window === parent.window;
};

class DraggableDom {
    static Style = {
        // width: 100px;
        // height: 100px;
        backgroundColor: "lightblue",
        border: "2px solid #4CAF50",
        margin: "10px",
        padding: "10px",
        cursor: "move",
        position: "absolute",
        zIndex: 100000,
        borderRadius: "5px",
    }
    constructor(cache = new Cache()) {
        this.cache = cache;
        this.toggleKey = this.cache.obj.toggleKey;
        this.show = false;
        this.id = `DD_${new Date().getTime()}`;
        this.dom = null;
        this.mark = null;
        if (this.cache.obj.height > window.innerHeight) {
            this.cache.set("height", 0);
        }
        if (this.cache.obj.width > window.innerWidth) {
            this.cache.set("width", 0);
        }
        this.style = {
            ...DraggableDom.Style,
            width: `${cache.obj.width}px`,
            height: `${cache.obj.height}px`,
            top: `${cache.obj.top}px`,
            left: `${cache.obj.left}px`,
            opacity: cache.obj.opacity ? cache.obj.opacity : 1,
        };
        this.createDom();
        this.dom.id = this.id;
        this.bindEvent();
    }
    getDom() {
        if (this.dom) {
            let dom = this.dom.getElementsByClassName('dom');
            if (dom.length) {
                return dom[0];
            }
        }
        return null;
    }
    createDom() {
        // <button accessKey="T" onClick="clickSubmit()">提交按钮</button>
        let btn = document.createElement('button');
        btn.style.display = 'none';
        btn.setAttribute("accessKey", this.toggleKey);
        btn.onclick = () => {
            if (this.dom) {
                this.dom.style.display = this.show ? 'unset' : 'none';
                this.show = !this.show;
            }
        };
        this.mark = document.createElement('div');
        this.mark.style.width = "100vw";
        this.mark.style.height = "100vh";
        this.mark.style.position = "fixed";
        this.mark.style.left = "0";
        this.mark.style.top = "0";
        this.mark.style.display = "none";
        this.mark.style.zIndex = this.style.zIndex - 1;
        this.dom = document.createElement('div');
        this.dom .setAttribute('draggable', "true");
        this.dom.innerHTML = `<div>按下 &lt;ALT+${this.toggleKey}&gt; 可以[隐藏/显示]当前选项框</div><div class="dom"></div>`
        for (let i in this.style) {
            this.dom.style[i] = this.style[i];
        }
        document.body.append(this.dom);
        document.body.append(this.mark);
        document.body.append(btn);
    }
    bindEvent() {
        // JavaScript 代码,实现拖拽功能
        let draggableElement = this.dom;
        let $this = this;
        draggableElement.ondragstart = function(event) {
            event.dataTransfer.setData('text/plain', event.target.id);
            $this.mark.style.display = 'block';
        }
        draggableElement.ondragend = function(event) {
            $this.mark.style.display = 'none';
        }

        document.body.ondragover = function(event) {
            event.preventDefault();
        };

        document.body.ondrop = function(event) {
            event.preventDefault();
            var data = event.dataTransfer.getData('text/plain');
            if (!data) {
                return;
            }
            var draggedElement = document.getElementById(data);

            // 获取鼠标位置
            var mouseX = event.clientX;
            var mouseY = event.clientY;

            // 设置元素的新位置
            draggedElement.style.left = mouseX - draggedElement.offsetWidth / 2 + 'px';
            draggedElement.style.top = mouseY - draggedElement.offsetHeight / 2 + 'px';
            $this.cache.set("top", parseFloat(draggedElement.style.top));
            $this.cache.set("left", parseFloat(draggedElement.style.left));
        };
    }
};

class Cache {
    static Reg = {
        Float: /^[0-9]+.[0-9]+$/,
        Int: /^[0-9]+$/,
    }
    static Default = {
        toggleKey: "X",
        top: 0,
        left: 0,
        width: 'unset',
        height: 'unset',
        speed: 1,
        Action: {},
        opacity: 1,
    };
    constructor(action) {
        let $this = this;
        this.key = "_video_speed_cache_";
        this.objType = {
            toggleKey(r) {
                r = r.toString();
                if (r.length > 1) {
                    return r[0];
                } else if (r.length < 1) {
                    return "X";
                } else {
                    return r;
                }
            },
            opacity(r) {
                r = $this.toNumberFloat(r);
                return r <= 0 ? 0.1 : (r > 1 ? 1 : r);
            },
            top: $this.toNumberFloat,
            left: $this.toNumberFloat,
            // width: 'unset',
            // height: 'unset',
            speed(r) {
                return $this.toNumberFloat(r, 1)
            },
        };
        this.obj = this.get() || {};
        for (let i in Cache.Default) {
            if (!(i in this.obj)) {
                this.obj[i] = Cache.Default[i];
            }
        }
    }
    toNumberFloat(r, dv) {
        r = parseFloat(r.toString());
        return Cache.Reg.Float.test(r.toFixed(1)) ? r : (dv||0);
    }
    toNumberInt(r, dv) {
        r = parseInt(r.toString());
        return Cache.Reg.Int.test(r) ? r : (dv||0);
    }
    set(key, value) {
        if (key in this.objType) {
            this.obj[key] = this.objType[key](value);
        } else {
            this.obj[key] = value;
        }
        this.store();
        return this;
    }
    store() {
        localStorage.setItem(this.key, JSON.stringify(this.obj));
    }
    checkData(data) {
        for (let i in this.objType) {
            data[i] = this.objType[i](data[i]);
        }
        return data;
    }
    get() {
        let data = localStorage.getItem(this.key);
        if (!data) {
            return null;
        }
        try {
            data = JSON.parse(data)
        } catch (e) {
            return null;
        } finally {
            return this.checkData(data);
        }
    }
    clear() {
        setTimeout(() => {
            localStorage.removeItem(this.key);
        }, 1000);
    }
}

class PlayDom {
    constructor(video, videoInstance, actions = []) {
        this.video = video;
        this.cache = new Cache();
        this.draggableDom = new DraggableDom(this.cache);
        this.action = {};

        let ret = this.buildAction(actions);

        this.createDom(ret.actionListForDom);
        ret.promise().then(() => {
            let fn = () => {
                let v = videoInstance.getVideo();
                if (v) {
                    video.playbackRate = this.cache.obj.speed;
                }
            }
            fn();
            setInterval(() => {
                fn();
            },500);
        });
    }

    buildAction(actions) {
        let cacheAction = this.cache.obj.Action;
        let actionList = [];
        let actionListForDom = {};
        let actionListForDom_ = [];
        actions.forEach(act => {
            this.action[act.name] = {
                action:()=>{},
                replay: false,
            }
            // act = { name: "", action() {}, group: "", run: false }
            if (!act.group) {
                act.group = act.name;
            }
            if (!(act.group in actionListForDom)) {
                actionListForDom[act.group] = [];
            }
            if (!(act.group in cacheAction)) {
                cacheAction[act.group] = "";
            } else if (cacheAction[act.group] === "") {
                cacheAction[act.group] = "none_" + new Date().getTime();
            }
            if (act.name === cacheAction[act.group]) {
                // 该主当前希望运行改方法
                actionList.push(act.action);
                cacheAction[act.group] = act.name;
                act.run = true;
                if (act.replay) {
                    this.action[act.name] = {
                        action: act.action,
                        replay: act.replay,
                    };
                }
            } else {
                if (act.run) {
                    if (cacheAction[act.group] === "") {
                        cacheAction[act.group] = act.name;
                        actionList.push(act.action);
                        if (act.replay) {
                            this.action[act.name] = {
                                action: act.action,
                                replay: act.replay,
                            };
                        }
                    } else {
                        act.run = false;
                        this.action[act.name] = {
                            action: act.action,
                            replay: false,
                        };
                    }
                } else {
                    this.action[act.name] = {
                        action: act.action,
                        replay: false,
                    };
                }
            }
            if (!act.replay) {
                this.action[act.name].replay = true;
            }
            actionListForDom[act.group].push({
                name: act.name,
                run: act.run
            });
        });
        for (let i in actionListForDom) {
            actionListForDom_.push({
                group: i,
                actions: actionListForDom[i]
            });
        }
        this.cache.obj.Action = Object.assign(this.cache.obj.Action, cacheAction);
        this.cache.store();
        return {
            promise: (function (actionLis, cache) {
                return new Promise(s => s()).then(() => {
                    actionList.forEach(act => act(true, cache));
                    return true;
                });
            }).bind(null,actionList,this.cache.obj),
            actionListForDom: actionListForDom_
        };
    }
    createDom(actionListForDom) {
        let styleDom = document.createElement('style');
        styleDom.innerHTML = `.play_dom_box {
        display: flex;
        padding: 5px;
    }
    .play_dom_btn {
        background: gainsboro;
        padding: 2px;
        margin-bottom: 2px;
        border-radius: 4px;
        width: 40px;
        text-align: center;
        cursor: pointer;
    }
    .play_dom_text {
        flex: 1;
        line-height: 80px;
        text-align: center;
    }`;
        let boxDom = document.createElement('div');
        boxDom.innerHTML = `
<div class="play_dom_box">
    <div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.1</div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.2</div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.5</div>
    </div>
    <div class="play_dom_text play_dom_text_speed">${this.cache.obj.speed}</div>
    <div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.1</div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.2</div>
        <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.5</div>
    </div>
</div>
<div style="display: flex;">透明 【<div class="play_dom_text_opacity">${this.cache.obj.opacity}</div>】 <div class="play_dom_btn play_dom_btn_opacity" tag="speed">-0.1</div>
&nbsp;&nbsp;&nbsp;<div class="play_dom_btn play_dom_btn_opacity" tag="speed">+0.1</div></div>
<div>
${actionListForDom.map(act => {
            // {
            //      group: "",
            //      actions: [{name: '', run: ''}]
            // }
            if (act.actions.length > 1) {
                let dom = act.actions.map(a => {
                    return `<input type="radio" tag="${a.name}" name="${act.group}" ${a.run ? 'checked' : ''} class="play_dom_check"/> ${a.name}`;
                }).join('');
                return `<div>${act.group} | ${dom}</div>`
            } else {
                let acti = act.actions[0];
                return `<div>${acti.name} <input tag="${acti.name}" type="checkbox" name="${act.group}" ${acti.run ? 'checked' : ''} class="play_dom_check"/></div>`
            }
        }).join(' ')}
</div>`;
        let dom = this.draggableDom.getDom();
        dom.append(styleDom);
        dom.append(boxDom);
        let $this = this;
        let speedTextDom = boxDom.getElementsByClassName('play_dom_text_speed')[0];
        let opacityTextDom = boxDom.getElementsByClassName('play_dom_text_opacity')[0];

        new Array(...boxDom.getElementsByClassName('play_dom_btn_opacity')).forEach(btn => {
            btn.onclick = function () {
                let sp = +this.innerText;
                $this.cache.set('opacity', (+$this.cache.obj.opacity + sp).toFixed(1));
                opacityTextDom.innerText = $this.cache.obj.opacity;
                $this.draggableDom.dom.style.opacity = $this.cache.obj.opacity;
            };
        });
        new Array(...boxDom.getElementsByClassName('play_dom_btn_speed')).forEach(btn => {
            btn.onclick = function () {
                let sp = +this.innerText;
                $this.cache.set('speed', (+$this.cache.obj.speed + sp).toFixed(2));
                speedTextDom.innerText = $this.cache.obj.speed;
                // $this.video.playbackRate = $this.cache.obj.speed;
            };
        });
        new Array(...boxDom.getElementsByClassName('play_dom_check')).forEach(btn => {
            btn.onchange = function () {
                let group = this.getAttribute('name');
                let actionName = this.getAttribute('tag');
                let currentAction = $this.action[actionName];
                if (this.checked) {
                    $this.cache.obj.Action[group] = actionName;
                    if (currentAction.replay) {
                        currentAction.action(true, $this.cache.obj);
                    } else {
                        currentAction.action(true, $this.cache.obj);
                        $this.action[actionName].action = () => {};
                    }
                } else {
                    $this.cache.obj.Action[group] = "";
                    if (currentAction.replay) {
                        currentAction.action(false, $this.cache.obj);
                    }
                }
                $this.cache.store();
            };
        });
    }
}

class Video {
    constructor() {
        this.baseDom = null;
        this.host =null;
    }
    getVideo() {
        let videos = document.getElementsByTagName(this.baseDom);
        let video = false;
        if (videos.length) {
            if (videos.length === 1) {
                video = videos[0];
            } else {
                let ww = parseInt(window.innerWidth * 0.4);
                let v = videos.filter(v => v.width > ww);
                if (v.length) {
                    video = v[0];
                }
            }
        }
        return video;
    }
    init() {
        return new Promise(s => {
            setTimeout(() => {
                this.host = location.hostname.split('.').reverse().slice(0, 2).reverse().join('.');
                let videos = document.getElementsByTagName('video');
                let video = false;
                if (videos.length) {
                    if (videos.length === 1) {
                        video = videos[0];
                    } else {
                        let ww = parseInt(window.innerWidth * 0.4);
                        let v = videos.filter(v => v.width > ww);
                        if (v.length) {
                            video = v[0];
                        }
                    }
                }
                if (video) {
                    this.baseDom = "video";
                } else {
                    if (this.host === "bilibili.com") {
                        let videos = document.getElementsByTagName('bwp-video');
                        if (videos.length) {
                            this.baseDom = "bwp-video";
                        }
                    }
                }
                s();
            }, 1000);
        })
    }
}
(function() {
    'use strict';
    if (checkWindow()) {
        let video = new Video();
        video.init().then(() => {
            let v = video.getVideo();
            if (v) {
                console.log("play dom");
                window.playDom = new PlayDom(v, video, [
                    ...(DefaultAction[video.host](video) || []),
                    {
                        group: "重置",
                        name: "重置",
                        run: false,
                        action() {
                            new Cache().clear();
                        }
                    },
                ]);
            }
        });
    }
    // Your code here...
})();