您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add Speed Slider to Youtube Player Settings
当前为
- // ==UserScript==
- // @name Youtube Player Speed Slider
- // @namespace youtube_player_speed_slider
- // @version 1.0.0
- // @description Add Speed Slider to Youtube Player Settings
- // @author Łukasz
- // @match https://*.youtube.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.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: '#f00',
- width: '20px',
- height: '20px',
- margin: '0',
- padding: '0',
- },
- attrs: {
- type: 'checkbox',
- title: 'Remember speed',
- checked: checked,
- },
- });
- }
- getValue() {
- return this.element.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 create(data) {
- const element = document.createElement(data.tag);
- if (typeof data.children === 'string') {
- element.innerHTML = data.children;
- } else if (data.children) {
- element.append(
- ...Dom.array(data.children).map((item) =>
- item instanceof HTMLElement ||
- item instanceof SVGElement
- ? item
- : Dom.create(item),
- ),
- );
- }
- 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,
- );
- if (typeof data.children === 'string') {
- element.innerHTML = data.children;
- } else if (data.children) {
- element.append(
- ...Dom.array(data.children).map((item) =>
- item instanceof SVGElement
- ? item
- : Dom.createSvg(item),
- ),
- );
- }
- 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);
- }
- }
- }
- exports.Dom = Dom;
- },
- 'Icon.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.Icon = void 0;
- const Component_1 = _require('Component.ts');
- const Dom_1 = _require('Dom.ts');
- const iconPath =
- 'M10.01,8v8l6-4L10,8L10,8z M6.3,5L5.7,4.2C7.2,3,9,2.2,11,2l0.1,1C9.3,3.2,7.7,3.9,6.3,5z M5,6.3L4.2,5.7C3,7.2,2.2,9,2,11 l1,.1C3.2,9.3,3.9,7.7,5,6.3z M5,17.7c-1.1-1.4-1.8-3.1-2-4.8L2,13c0.2,2,1,3.8,2.2,5.4L5,17.7z M11.1,21c-1.8-0.2-3.4-0.9-4.8-2 l-0.6,.8C7.2,21,9,21.8,11,22L11.1,21z M22,12c0-5.2-3.9-9.4-9-10l-0.1,1c4.6,.5,8.1,4.3,8.1,9s-3.5,8.5-8.1,9l0.1,1 C18.2,21.5,22,17.2,22,12z';
- class Icon extends Component_1.default {
- constructor() {
- super('div', {
- classes: 'ytp-menuitem-icon',
- children: Dom_1.Dom.createSvg({
- tag: 'svg',
- attrs: {
- height: '24',
- width: '24',
- viewBox: '0 0 24 24',
- },
- children: Dom_1.Dom.createSvg({
- tag: 'path',
- attrs: {
- fill: 'white',
- d: iconPath,
- },
- }),
- }),
- });
- }
- }
- exports.Icon = Icon;
- },
- 'Label.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.Label = void 0;
- const Component_1 = _require('Component.ts');
- class Label extends Component_1.default {
- constructor(speed, label = 'Speed') {
- super('div', {classes: 'ytp-menuitem-label'});
- this.speed = '1.0';
- this.label = label;
- this.updateSpeed(speed);
- }
- updateLabel(label = 'Speed') {
- this.label = label;
- this.updateText();
- }
- updateSpeed(speed) {
- this.speed = speed.toFixed(1);
- this.updateText();
- }
- updateText() {
- this.element.innerText = `${this.label}: ${this.speed}`;
- }
- }
- exports.Label = Label;
- },
- 'Menu.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.Menu = void 0;
- const SpeedMenuItem_1 = _require('SpeedMenuItem.ts');
- const delay_1 = _require('delay.ts');
- class Menu {
- constructor() {
- this.getMenu();
- }
- getMenu() {
- return document.querySelector(
- '.ytp-settings-menu .ytp-panel-menu',
- );
- }
- getDefaultMenuItem() {
- const defaultSpeedItem = [
- ...document.querySelectorAll('.ytp-menuitem'),
- ].filter((e) => {
- var _a;
- const path =
- (_a = e.querySelector('.ytp-menuitem-icon path')) ===
- null || _a === void 0
- ? void 0
- : _a.getAttribute('d');
- return path === null || path === void 0
- ? void 0
- : path.startsWith('M10,8v8l6-4L10,');
- });
- if (defaultSpeedItem.length) {
- return defaultSpeedItem[0];
- }
- return undefined;
- }
- getLabel() {
- var _a;
- const label =
- (_a = this.getDefaultMenuItem()) === null || _a === void 0
- ? void 0
- : _a.querySelector('.ytp-menuitem-label');
- return label === null || label === void 0
- ? void 0
- : label.innerText;
- }
- async reopenMenu() {
- var _a, _b;
- const menuButton = document.querySelector(
- '.ytp-settings-button',
- );
- const menu = this.getMenu();
- if (menu && this.menuHasCustomItem(menu)) {
- return;
- }
- if (menuButton) {
- (_a =
- menu === null || menu === void 0
- ? void 0
- : menu.style) === null || _a === void 0
- ? void 0
- : _a.setProperty('opacity', '0');
- menuButton.click();
- await (0, delay_1.delay)(50);
- menuButton.click();
- (_b =
- menu === null || menu === void 0
- ? void 0
- : menu.style) === null || _b === void 0
- ? void 0
- : _b.setProperty('opacity', '1');
- await (0, delay_1.delay)(50);
- }
- }
- menuHasCustomItem(menu) {
- return Boolean(
- menu.querySelector(`#${SpeedMenuItem_1.SpeedMenuItem.ID}`),
- );
- }
- addCustomSpeedItem(item) {
- var _a;
- const menu = this.getMenu();
- const defaultItem = this.getDefaultMenuItem();
- if (menu === null) {
- return false;
- }
- if (this.menuHasCustomItem(menu)) {
- (_a =
- defaultItem === null || defaultItem === void 0
- ? void 0
- : defaultItem.parentNode) === null || _a === void 0
- ? void 0
- : _a.removeChild(defaultItem);
- return true;
- }
- if (defaultItem) {
- defaultItem.replaceWith(item.getElement());
- } else {
- menu.appendChild(item.getElement());
- }
- return true;
- }
- }
- exports.Menu = Menu;
- },
- 'Player.ts': (_unused_module, exports) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.Player = void 0;
- class Player {
- constructor(speed) {
- this.speed = speed;
- this.player = null;
- this.setSpeed(this.speed);
- }
- getPlayer() {
- if (!this.player) {
- this.player = document.querySelector('.html5-main-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 = 'yts-listener';
- },
- 'Slider.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.Slider = void 0;
- const Component_1 = _require('Component.ts');
- class Slider extends Component_1.default {
- constructor(speed) {
- super('input', {
- attrs: {
- type: 'range',
- min: Slider.MIN_VALUE,
- max: Slider.MAX_VALUE,
- step: 0.05,
- value: speed.toString(),
- },
- styles: {
- accentColor: '#f00',
- width: 'calc(100% - 30px)',
- margin: '0 5px',
- padding: '0',
- },
- });
- }
- setSpeed(speed) {
- this.element.value = speed.toString();
- }
- getSpeed() {
- return parseFloat(this.element.value);
- }
- }
- exports.Slider = Slider;
- Slider.MIN_VALUE = 0.5;
- Slider.MAX_VALUE = 4;
- },
- 'SpeedMenuItem.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.SpeedMenuItem = void 0;
- const Component_1 = _require('Component.ts');
- const Dom_1 = _require('Dom.ts');
- class SpeedMenuItem extends Component_1.default {
- constructor() {
- super('div', {
- classes: 'ytp-menuitem',
- attrs: {
- id: SpeedMenuItem.ID,
- },
- });
- this.wrapper = Dom_1.Dom.element('div', 'ytp-menuitem-content');
- }
- addElement(icon, label, slider, checkbox) {
- this.element.append(icon, label, this.wrapper);
- this.wrapper.append(checkbox, slider);
- }
- }
- exports.SpeedMenuItem = SpeedMenuItem;
- SpeedMenuItem.ID = 'yts-speed-menu-item';
- },
- 'AppController.ts': (_unused_module, exports, _require) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.AppController = void 0;
- const Icon_1 = _require('Icon.ts');
- const Label_1 = _require('Label.ts');
- const Slider_1 = _require('Slider.ts');
- const Checkbox_1 = _require('Checkbox.ts');
- const Store_1 = _require('Store.ts');
- const SpeedMenuItem_1 = _require('SpeedMenuItem.ts');
- const Menu_1 = _require('Menu.ts');
- const Player_1 = _require('Player.ts');
- const Observer_1 = _require('Observer.ts');
- class AppController {
- constructor() {
- this.rememberSpeed = new Store_1.Store('yts-remember-speed');
- this.speed = new Store_1.Store('yts-speed');
- const initialSpeed = this.getSpeed();
- this.menu = new Menu_1.Menu();
- this.player = new Player_1.Player(initialSpeed);
- this.speedMenuItem = new SpeedMenuItem_1.SpeedMenuItem();
- this.icon = new Icon_1.Icon();
- this.label = new Label_1.Label(initialSpeed);
- this.slider = new Slider_1.Slider(initialSpeed);
- this.checkbox = new Checkbox_1.Checkbox(
- this.rememberSpeed.get(false),
- );
- this.observer = new Observer_1.Observer();
- this.speedMenuItem.addElement(
- this.icon.getElement(),
- this.label.getElement(),
- this.slider.getElement(),
- this.checkbox.getElement(),
- );
- this.initEvents();
- }
- initEvents() {
- this.slider.event('change', this.sliderChangeEvent.bind(this));
- this.slider.event('input', this.sliderChangeEvent.bind(this));
- this.slider.event('wheel', this.sliderWheelEvent.bind(this));
- this.checkbox.event('change', this.checkboxEvent.bind(this));
- document.addEventListener('spfdone', this.initApp.bind(this));
- }
- sliderChangeEvent(_) {
- this.updateSpeed(this.slider.getSpeed());
- }
- checkboxEvent(_) {
- this.rememberSpeed.set(this.checkbox.getValue());
- }
- sliderWheelEvent(event) {
- const current = this.slider.getSpeed();
- const diff = event.deltaY > 0 ? -0.05 : 0.05;
- const value = Math.max(
- Slider_1.Slider.MIN_VALUE,
- Math.min(current + diff, Slider_1.Slider.MAX_VALUE),
- );
- if (current != value) {
- this.slider.setSpeed(value);
- this.updateSpeed(value);
- }
- event.preventDefault();
- }
- updateSpeed(speed) {
- this.speed.set(speed);
- this.player.setSpeed(speed);
- this.label.updateSpeed(speed);
- }
- getSpeed() {
- return this.rememberSpeed.get() ? this.speed.get(1) : 1;
- }
- mutationCallback() {
- this.initApp();
- }
- async initApp() {
- this.player.setSpeed(this.getSpeed());
- await this.menu.reopenMenu();
- const label = this.menu.getLabel();
- if (label) {
- this.label.updateLabel(label);
- }
- const player = this.player.getPlayer();
- if (player) {
- this.observer.start(
- player,
- this.mutationCallback.bind(this),
- );
- }
- return this.menu.addCustomSpeedItem(this.speedMenuItem);
- }
- }
- exports.AppController = AppController;
- },
- '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;
- },
- 'delay.ts': (_unused_module, exports) => {
- Object.defineProperty(exports, '__esModule', {value: true});
- exports.delay = void 0;
- async function delay(ms = 1000) {
- return await new Promise((resolve) => {
- setTimeout(resolve, ms);
- });
- }
- exports.delay = delay;
- },
- };
- 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();
- async function init() {
- const ok = await app.initApp();
- if (!ok) {
- window.setTimeout(init, 2000);
- }
- }
- document.addEventListener('spfdone', init);
- init();
- })();
- })();