您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add speed buttons to any HTML5 <video> element. Comes with a loader for YouTube and Vimeo
当前为
// ==UserScript== // @name Video Speed Buttons // @description Add speed buttons to any HTML5 <video> element. Comes with a loader for YouTube and Vimeo // @namespace bradenscode // @version 1.0.4 // @copyright 2017, Braden Best // @run-at document-end // @author Braden Best // @grant none // // @match *://*.youtube.com/* // @match *://*.vimeo.com/* // ==/UserScript== // To add a new site: add a @match above, and modify loader_data.container_candidates near the bottom function video_speed_buttons(anchor, video_el){ if(!anchor || !video_el) return null; const COLOR_SELECTED = "black", COLOR_NORMAL = "grey", BUTTON_SIZE = "100%", DEFAULT_SPEED = 1.0, LABEL_TEXT = "Video Speed: "; const BUTTON_TEMPLATES = [ ["25%", 0.25], ["50%", 0.5], ["Normal", 1], ["1.5x", 1.5], ["2x", 2], ["3x", 3], ["4x", 4], ["8x", 8], ["16x", 16] ]; const buttons = { head: null, selected: null, last: null }; const keyboard_controls = [ [",", "Speed Down", function(ev){ if(is_comment_box(ev.target)) return false; (buttons.selected || buttons.head) .getprev() .el .dispatchEvent(new MouseEvent("click")); }], [".", "Speed Up", function(ev){ if(is_comment_box(ev.target)) return false; (buttons.selected || buttons.head) .getnext() .el .dispatchEvent(new MouseEvent("click")); }], ["?", "Show Help", function(ev){ var infobox; if(is_comment_box(ev.target)) return false; (infobox = Infobox(container)) .log("Keyboard Controls (click to close)<br>"); keyboard_controls.forEach(function([key, description]){ infobox.log(" [.s] .s<br>" .replace(".s", key) .replace(".s", description)); }); }] ]; const container = (function(){ var div = document.createElement("div"); var prev_node = null; div.className = "vsb-container"; div.style.borderBottom = "1px solid #ccc"; div.style.marginBottom = "10px"; div.style.paddingBottom = "10px"; div.appendChild(document.createTextNode(LABEL_TEXT)); BUTTON_TEMPLATES.forEach(function(button){ var speedButton = SpeedButton(...button, div); if(buttons.head === null) buttons.head = speedButton; if(prev_node !== null){ speedButton.prev = prev_node; prev_node.next = speedButton; } prev_node = speedButton; if(speedButton.speed == DEFAULT_SPEED) speedButton.select(); }); return div; })(); function is_comment_box(el){ const candidate = [ ".comment-simplebox-text", "textarea" ].map(c => document.querySelector(c)) .find(el => el !== null); if(candidate === null){ logvsb("video_speed_buttons::is_comment_box", "no candidate for comment box. Assuming false."); return 0; } return el === candidate; } function Infobox(parent){ var el = document.createElement("pre"); el.style.font = "1em monospace"; el.style.borderTop = "1px solid #ccc"; el.style.marginTop = "10px"; el.style.paddingTop = "10px"; el.addEventListener("click", function(){ parent.removeChild(el); }); parent.appendChild(el); function log(msg){ el.innerHTML += msg; } return { el, log }; } function setPlaybackRate(el, rate){ if(el) el.playbackRate = rate; else logvsb("video_speed_buttons::setPlaybackRate", "video element is null or undefined", 1); } function SpeedButton(text, speed, parent){ var el = document.createElement("span"); var self; el.innerHTML = text; el.style.marginRight = "10px"; el.style.fontWeight = "bold"; el.style.fontSize = BUTTON_SIZE; el.style.color = COLOR_NORMAL; el.style.cursor = "pointer"; el.addEventListener("click", function(){ setPlaybackRate(video_el, speed); self.select(); }); parent.appendChild(el); function select(){ if(buttons.last !== null) buttons.last.el.style.color = COLOR_NORMAL; buttons.last = self; buttons.selected = self; el.style.color = COLOR_SELECTED; } function getprev(){ if(self.prev === null) return self; return buttons.selected = self.prev; } function getnext(){ if(self.next === null) return self; return buttons.selected = self.next; } return self = { el, text, speed, prev: null, next: null, select, getprev, getnext }; } function kill(){ anchor.removeChild(container); document.body.removeEventListener("keydown", ev_keyboard); } function ev_keyboard(ev){ let match = keyboard_controls.find(([key, unused, callback]) => key === ev.key); let callback = (match || {2: ()=>null})[2]; callback(ev); } setPlaybackRate(video_el, DEFAULT_SPEED); anchor.insertBefore(container, anchor.firstChild); document.body.addEventListener("keydown", ev_keyboard); return { controls: keyboard_controls, buttons, kill, SpeedButton, Infobox, setPlaybackRate, is_comment_box }; } video_speed_buttons.from_query = function(anchor_q, video_q){ return video_speed_buttons( document.querySelector(anchor_q), document.querySelector(video_q)); } // Multi-purpose Loader (defaults to floating on top right) const loader_data = { container_candidates: [ // YouTube "div#container.ytd-video-primary-info-renderer", "div#watch-header", "div#watch7-headline", "div#watch-headline-title", // Vimeo ".clip_info-wrapper", ], css_div: [ "position: fixed", "top: 0", "right: 0", "zIndex: 100", "background: rgba(0, 0, 0, 0.8)", "color: #eeeeee", "padding: 10px" ].map(rule => rule.split(/: */)), css_vsb_container: [ "borderBottom: none", "marginBottom: 0", "paddingBottom: 0", ].map(rule => rule.split(/: */)) }; function logvsb(where, msg, lvl = 0){ let fmt = "[vsb::$where] $msg" .replace("$where", where) .replace("$msg", msg); let logf = (["info", "error"])[lvl]; console[logf](fmt); } function loader_loop(){ let vsbc = () => document.querySelector(".vsb-container"); let candidate; let default_candidate; if(vsbc() !== null) return; candidate = loader_data .container_candidates .map(candidate => document.querySelector(candidate)) .find(candidate => candidate !== null); default_candidate = (function(){ let el = document.createElement("div"); loader_data.css_div.forEach(function([name, value]){ el.style[name] = value; }); document.body.appendChild(el); return el; }()); video_speed_buttons(candidate || default_candidate, document.querySelector("video")); if(candidate === null){ logvsb("loader_loop", "no candidates for title section. Defaulting to top of page."); loader_data.css_vsb_container.forEach(function([name, value]){ vsbc().style[name] = value; }); } } setInterval(function(){ if(document.readyState === "complete") setTimeout(loader_loop, 1000); }, 1000); // Blame YouTube for this