Vimeo Player Speed Slider

Add Speed Slider to Vimeo Player Settings

当前为 2024-11-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Vimeo Player Speed Slider
// @namespace    vimeo_player_speed_slider
// @version      1.1.1
// @description  Add Speed Slider to Vimeo Player Settings
// @author       Łukasz
// @include      https://*.vimeo.com/*
// @include      https://vimeo.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=vimeo.com
// @grant        none
// ==/UserScript==

(() => {
    'use strict';
    var _modules = {
        'Checkbox.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Checkbox = void 0;
            const Component_1 = _require('Component.ts');
            class Checkbox extends Component_1.default {
                constructor(checked) {
                    super('input', {
                        styles: {
                            accentColor: 'var(--color-two)',
                            appearance: 'auto',
                            width: '16px',
                            height: '16px',
                            margin: '0',
                            padding: '0',
                        },
                        attrs: {
                            type: 'checkbox',
                            title: 'Remember speed',
                            checked: checked,
                        },
                    });
                }
                initEvents(onChange) {
                    this.event('change', () => onChange(this.element.checked));
                }
                setValue(checked) {
                    this.element.checked = checked;
                }
            }
            exports.Checkbox = Checkbox;
        },

        'Component.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            const Dom_1 = _require('Dom.ts');
            class Component {
                constructor(tag, props = {}) {
                    this.element = Dom_1.Dom.create({tag, ...props});
                }
                addClassName(...className) {
                    this.element.classList.add(...className);
                }
                event(event, callback) {
                    this.element.addEventListener(event, callback);
                }
                getElement() {
                    return this.element;
                }
                mount(parent) {
                    parent.appendChild(this.element);
                }
            }
            exports['default'] = Component;
        },

        'Dom.ts': (_unused_module, exports) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Dom = void 0;
            class Dom {
                static appendChildren(element, children) {
                    if (typeof children === 'string') {
                        element.innerHTML = children;
                    } else if (children) {
                        element.append(
                            ...Dom.array(children).map((item) => {
                                if (
                                    item instanceof HTMLElement ||
                                    item instanceof SVGElement
                                ) {
                                    return item;
                                }
                                if (Dom.isSvgItem(item)) {
                                    return Dom.createSvg(item);
                                }
                                return Dom.create(item);
                            }),
                        );
                    }
                }
                static create(data) {
                    const element = document.createElement(data.tag);
                    Dom.appendChildren(element, data.children);
                    Dom.applyClass(element, data.classes);
                    Dom.applyAttrs(element, data.attrs);
                    Dom.applyEvents(element, data.events);
                    Dom.applyStyles(element, data.styles);
                    return element;
                }
                static element(tag, classes, children) {
                    return Dom.create({tag, classes, children});
                }
                static createSvg(data) {
                    const element = document.createElementNS(
                        'http://www.w3.org/2000/svg',
                        data.tag,
                    );
                    Dom.appendChildren(element, data.children);
                    Dom.applyClass(element, data.classes);
                    Dom.applyAttrs(element, data.attrs);
                    Dom.applyEvents(element, data.events);
                    Dom.applyStyles(element, data.styles);
                    return element;
                }
                static array(element) {
                    return Array.isArray(element) ? element : [element];
                }
                static elementSvg(tag, classes, children) {
                    return Dom.createSvg({tag, classes, children});
                }
                static applyAttrs(element, attrs) {
                    if (attrs) {
                        Object.entries(attrs).forEach(([key, value]) => {
                            if (value === undefined || value === false) {
                                element.removeAttribute(key);
                            } else {
                                element.setAttribute(key, `${value}`);
                            }
                        });
                    }
                }
                static applyStyles(element, styles) {
                    if (styles) {
                        Object.entries(styles).forEach(([key, value]) => {
                            const name = key.replace(
                                /[A-Z]/g,
                                (c) => `-${c.toLowerCase()}`,
                            );
                            element.style.setProperty(name, value);
                        });
                    }
                }
                static applyEvents(element, events) {
                    if (events) {
                        Object.entries(events).forEach(([name, callback]) => {
                            element.addEventListener(name, callback);
                        });
                    }
                }
                static applyClass(element, classes) {
                    if (classes) {
                        element.setAttribute('class', classes);
                    }
                }
                static isSvgItem(item) {
                    try {
                        const element = document.createElementNS(
                            'http://www.w3.org/2000/svg',
                            item.tag,
                        );
                        return element.namespaceURI === 'http://www.w3.org/2000/svg';
                    } catch (error) {
                        return false;
                    }
                }
            }
            exports.Dom = Dom;
        },

        'Elements.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Elements = void 0;
            const MenuItem_1 = _require('MenuItem.ts');
            class Elements {
                static ref(selector) {
                    return document.querySelector(selector);
                }
                static menu() {
                    return Elements.ref(
                        '[data-menu="prefs"] [class^=Menu_module_menuPanel]',
                    );
                }
                static menuItem() {
                    return Elements.ref(
                        '[data-menu="prefs"] [class^=Menu_module_menuPanel] [class^=MenuOption_module_option]',
                    );
                }
                static menuItemWithLabel(labels) {
                    const optionItems = [
                        ...document.querySelectorAll(
                            '[data-menu="prefs"] [class^=MenuOption_module_option]',
                        ),
                    ];
                    return optionItems.find(
                        (e) =>
                            e.id !== MenuItem_1.MenuItem.ID &&
                            labels.some((text) => e.innerText.includes(text)),
                    );
                }
                static menuSpeedItem() {
                    return Elements.menuItemWithLabel([
                        'Speed',
                        'Velocidad',
                        'Geschwindigkeit',
                        'Vitesse',
                        'Velocidade',
                        'スピード',
                        '속도',
                    ]);
                }
                static menuQualityItem() {
                    return Elements.menuItemWithLabel([
                        'Quality',
                        'Calidad',
                        'Qualität',
                        'Qualité',
                        'Qualidade',
                        '画質',
                        '고화질',
                    ]);
                }
                static menuSpeedLabel() {
                    var _a;
                    return (_a = Elements.menuSpeedItem()) === null || _a === void 0
                        ? void 0
                        : _a.querySelector('span');
                }
                static video() {
                    return Elements.ref('.vp-video video');
                }
            }
            exports.Elements = Elements;
        },

        'Label.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Label = void 0;
            const Component_1 = _require('Component.ts');
            const Elements_1 = _require('Elements.ts');
            class Label extends Component_1.default {
                constructor() {
                    super('span');
                    this.label = 'Speed';
                    this.speed = '1.0';
                }
                init() {
                    const originalItemLabel = Elements_1.Elements.menuSpeedLabel();
                    if (originalItemLabel) {
                        this.label = originalItemLabel.innerText;
                        this.element.className = originalItemLabel.className;
                        this.render();
                    }
                }
                setSpeed(speed) {
                    this.speed = speed.toFixed(1);
                    this.render();
                }
                render() {
                    this.element.innerText = `${this.label}: ${this.speed}`;
                }
            }
            exports.Label = Label;
        },

        'MenuItem.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.MenuItem = void 0;
            const Component_1 = _require('Component.ts');
            const Elements_1 = _require('Elements.ts');
            const Slider_1 = _require('Slider.ts');
            const Checkbox_1 = _require('Checkbox.ts');
            const Label_1 = _require('Label.ts');
            const Dom_1 = _require('Dom.ts');
            class MenuItem extends Component_1.default {
                constructor(setSpeed, setRemember) {
                    super('div', {attrs: {id: MenuItem.ID}});
                    this.checkbox = new Checkbox_1.Checkbox(false);
                    this.slider = new Slider_1.Slider();
                    this.label = new Label_1.Label();
                    this.wrapper = Dom_1.Dom.create({
                        tag: 'div',
                        styles: {
                            display: 'flex',
                            alignItems: 'center',
                        },
                    });
                    this.wrapper.append(
                        this.checkbox.getElement(),
                        this.slider.getElement(),
                    );
                    this.element.append(this.label.getElement(), this.wrapper);
                    this.slider.initEvents(setSpeed);
                    this.checkbox.initEvents(setRemember);
                }
                setSpeed(speed) {
                    this.slider.setSpeed(speed);
                    this.label.setSpeed(speed);
                }
                setRemember(state) {
                    this.checkbox.setValue(state);
                }
                mountItem() {
                    var _a, _b;
                    const originalSpeedItem = Elements_1.Elements.menuSpeedItem();
                    const originalQualityItem =
                        Elements_1.Elements.menuQualityItem();
                    if (!originalSpeedItem && !originalQualityItem) {
                        (_a = this.element.parentNode) === null || _a === void 0
                            ? void 0
                            : _a.removeChild(this.element);
                        return;
                    }
                    originalSpeedItem === null || originalSpeedItem === void 0
                        ? void 0
                        : originalSpeedItem.style.setProperty('display', 'none');
                    if (!this.element.parentNode) {
                        if (originalSpeedItem) {
                            originalSpeedItem.after(this.element);
                        } else if (originalQualityItem) {
                            originalQualityItem.after(this.element);
                        }
                        this.label.init();
                        this.element.className =
                            ((_b = Elements_1.Elements.menuItem()) === null ||
                            _b === void 0
                                ? void 0
                                : _b.className) || this.element.className;
                    }
                }
            }
            exports.MenuItem = MenuItem;
            MenuItem.ID = 'vis-menu-speed-item';
        },

        'Player.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Player = void 0;
            const Elements_1 = _require('Elements.ts');
            class Player {
                constructor() {
                    this.player = null;
                    this.speed = 1;
                }
                getPlayer() {
                    if (!this.player) {
                        this.player = Elements_1.Elements.video();
                        if (this.player) {
                            this.initEvent(this.player);
                        }
                    }
                    return this.player;
                }
                initEvent(player) {
                    if (!player.getAttribute(Player.READY_FLAG)) {
                        player.addEventListener(
                            'ratechange',
                            this.checkPlayerSpeed.bind(this),
                        );
                        player.setAttribute(Player.READY_FLAG, 'ready');
                    }
                }
                checkPlayerSpeed() {
                    const player = this.getPlayer();
                    if (
                        player &&
                        Math.abs(player.playbackRate - this.speed) > 0.01
                    ) {
                        player.playbackRate = this.speed;
                        setTimeout(this.checkPlayerSpeed.bind(this), 200);
                    }
                }
                setSpeed(speed) {
                    this.speed = speed;
                    const player = this.getPlayer();
                    if (player !== null) {
                        player.playbackRate = speed;
                    }
                }
            }
            exports.Player = Player;
            Player.READY_FLAG = 'vis-listener';
        },

        'Slider.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Slider = void 0;
            const Component_1 = _require('Component.ts');
            const GlobalStyle_1 = _require('GlobalStyle.ts');
            class Slider extends Component_1.default {
                constructor() {
                    super('input', {
                        classes: 'vis-slider',
                        attrs: {
                            type: 'range',
                            min: Slider.MIN_VALUE,
                            max: Slider.MAX_VALUE,
                            step: 0.05,
                        },
                        styles: {
                            background: '#ffffff66',
                            width: 'calc(100% - 30px)',
                            height: '6px',
                            outline: 'none',
                            margin: '0 10px',
                            padding: '0',
                            borderRadius: '3px',
                        },
                    });
                    GlobalStyle_1.GlobalStyle.addStyle(
                        'vis-slider',
                        `input[type='range'].vis-slider {
              -webkit-appearance: none;
            }

            input[type='range'].vis-slider::-moz-range-thumb ,
            input[type='range'].vis-slider::-webkit-slider-thumb {
              -webkit-appearance: none;
              appearance: none;
              width: 12px;
              height: 12px;
              border-radius: 6px;
              background: #fff;
              cursor: pointer;
              margin-top: -2px;
            }`,
                    );
                }
                initEvents(onChange) {
                    this.event('change', () => onChange(this.getSpeed()));
                    this.event('input', () => onChange(this.getSpeed()));
                    this.event('wheel', (event) => {
                        event.stopPropagation();
                        event.preventDefault();
                        const diff = event.deltaY > 0 ? -0.05 : 0.05;
                        onChange(this.getSpeed() + diff);
                        return false;
                    });
                }
                setSpeed(speed) {
                    this.updateBg(speed);
                    this.element.value = speed.toString();
                }
                getSpeed() {
                    return parseFloat(this.element.value);
                }
                updateBg(value) {
                    const progress =
                        ((value - Slider.MIN_VALUE) /
                            (Slider.MAX_VALUE - Slider.MIN_VALUE)) *
                        100;
                    this.element.style.background =
                        'linear-gradient(to right, COLOR1 0%, COLOR1 STEP%, COLOR2 STEP%, COLOR2 100%)'
                            .replaceAll('COLOR1', 'var(--color-two)')
                            .replaceAll('COLOR2', '#ffffff66')
                            .replaceAll('STEP', progress.toFixed(1));
                }
            }
            exports.Slider = Slider;
            Slider.MIN_VALUE = 0.5;
            Slider.MAX_VALUE = 4;
        },

        'AppController.ts': (_unused_module, exports, _require) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.AppController = void 0;
            const Store_1 = _require('Store.ts');
            const MenuItem_1 = _require('MenuItem.ts');
            const Player_1 = _require('Player.ts');
            const Observer_1 = _require('Observer.ts');
            const Elements_1 = _require('Elements.ts');
            class AppController {
                constructor() {
                    this.player = new Player_1.Player();
                    this.videoObserver = new Observer_1.Observer();
                    this.menuObserver = new Observer_1.Observer();
                    this.rememberSpeed = new Store_1.Store('vis-remember-speed');
                    this.speed = new Store_1.Store('vis-speed');
                    this.item = new MenuItem_1.MenuItem(
                        this.setSpeed.bind(this),
                        this.setRemember.bind(this),
                    );
                    this.setSpeed(this.getSpeed());
                    this.setRemember(this.rememberSpeed.get(false));
                }
                setSpeed(speed) {
                    this.speed.set(speed);
                    this.player.setSpeed(speed);
                    this.item.setSpeed(speed);
                }
                setRemember(state) {
                    this.rememberSpeed.set(state);
                    this.item.setRemember(state);
                }
                getSpeed() {
                    return this.rememberSpeed.get(false) ? this.speed.get(1) : 1;
                }
                mount() {
                    this.item.mountItem();
                }
                init() {
                    const video = Elements_1.Elements.video();
                    const menu = Elements_1.Elements.menu();
                    if (video && menu) {
                        this.videoObserver.start(video, this.mount.bind(this));
                        this.menuObserver.start(menu, this.mount.bind(this));
                        this.mount();
                        this.setSpeed(this.getSpeed());
                        return true;
                    }
                    return false;
                }
            }
            exports.AppController = AppController;
        },

        'GlobalStyle.ts': (_unused_module, exports) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.GlobalStyle = void 0;
            class GlobalStyle {
                static addStyle(key, styles) {
                    const style =
                        document.getElementById(key) ||
                        (function () {
                            const style = document.createElement('style');
                            style.id = key;
                            document.head.appendChild(style);
                            return style;
                        })();
                    style.textContent = styles;
                }
            }
            exports.GlobalStyle = GlobalStyle;
        },

        'Observer.ts': (_unused_module, exports) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Observer = void 0;
            class Observer {
                stop() {
                    if (this.observer) {
                        this.observer.disconnect();
                    }
                }
                start(element, callback) {
                    this.stop();
                    this.observer = new MutationObserver(callback);
                    this.observer.observe(element, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        characterData: true,
                        attributeOldValue: true,
                        characterDataOldValue: true,
                    });
                }
            }
            exports.Observer = Observer;
        },

        'Store.ts': (_unused_module, exports) => {
            Object.defineProperty(exports, '__esModule', {value: true});
            exports.Store = void 0;
            class Store {
                constructor(key) {
                    this.key = key;
                }
                encode(val) {
                    return JSON.stringify(val);
                }
                decode(val) {
                    return JSON.parse(val);
                }
                set(value) {
                    try {
                        localStorage.setItem(this.key, this.encode(value));
                    } catch (e) {
                        return;
                    }
                }
                get(defaultValue = undefined) {
                    try {
                        const data = localStorage.getItem(this.key);
                        if (data) {
                            return this.decode(data);
                        }
                        return defaultValue;
                    } catch (e) {
                        return defaultValue;
                    }
                }
                remove() {
                    localStorage.removeItem(this.key);
                }
            }
            exports.Store = Store;
        },
    };

    var _module_cache = {};

    function _require(moduleId) {
        var cachedModule = _module_cache[moduleId];
        if (cachedModule !== undefined) {
            return cachedModule.exports;
        }

        var module = (_module_cache[moduleId] = {
            exports: {},
        });

        _modules[moduleId](module, module.exports, _require);

        return module.exports;
    }

    var _exports = {};

    (() => {
        var exports = _exports;
        var _unused_export;

        _unused_export = {value: true};
        const AppController_1 = _require('AppController.ts');
        const app = new AppController_1.AppController();
        let attempt = 0;
        function init() {
            if (attempt <= 4 && !app.init()) {
                attempt++;
                window.setTimeout(init, 2000);
            }
        }
        init();
    })();
})();