Advertisement
Guest User

Untitled

a guest
Mar 3rd, 2020
169
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name         Video Speed Buttons
  3. // @description  Add speed buttons to any HTML5 <video> element. Comes with a loader for YouTube and Vimeo
  4. // @namespace    bradenscode
  5. // @version      1.0.9.2
  6. // @copyright    2017, Braden Best
  7. // @run-at       document-end
  8. // @author       Braden Best
  9. // @grant        none
  10. //
  11. // @match        *://*.youtube.com/*
  12. // @match        *://*.vimeo.com/*
  13. // ==/UserScript==
  14.  
  15. // warning: vimeo support is broken. A fix will be added in a future patch
  16.  
  17. // To add a new site: add a @match above, and modify loader_data.container_candidates near the bottom
  18.  
  19. const speedperchannel = {
  20.     "Blitz": 2,
  21.     "Bryce Edward Brown": 1.75,
  22.     "ExplainingComputers": 2
  23. }
  24.    
  25. function getspeed(){
  26.     var channelname;
  27.     if (location.pathname === "/watch" && (channelname = document.querySelector("#primary #meta #channel-name #text"))){
  28.         channelname = channelname.innerText.trim();
  29.     } else {
  30.         channelname = "";
  31.     }
  32.     if (channelname in speedperchannel) return speedperchannel[channelname];
  33.     if (document.querySelector("#container .badge-style-type-verified-artist")) return 1;
  34.     return 1.5;
  35. }
  36.  
  37. function video_speed_buttons(anchor, video_el){
  38.     if(!anchor || !video_el)
  39.         return null;
  40.    
  41.  
  42.     const COLOR_SELECTED = "red",
  43.         COLOR_NORMAL = "#dcdcdc",
  44.         BUTTON_SIZE = "80%",
  45.         LABEL_TEXT = "";
  46.  
  47.     const BUTTON_TEMPLATES = [
  48.         ["50%", 0.5],
  49.         ["75%", 0.75],
  50.         ["Normal", 1],
  51.         ["1.25x", 1.25],
  52.         ["1.5x", 1.5],
  53.         ["1.75x", 1.75],
  54.         ["2x", 2],
  55.         ["2.5x", 2.5],
  56.         ["3x", 3],
  57.         ["4x", 4]
  58.     ];
  59.  
  60.     const buttons = {
  61.         head:      null,
  62.         selected:  null,
  63.         last:      null
  64.     };
  65.  
  66.     const keyboard_controls = [
  67.         ["-", "Speed Down", function(ev){
  68.             if(is_comment_box(ev.target))
  69.                 return false;
  70.  
  71.             (buttons.selected || buttons.head)
  72.                 .getprev()
  73.                 .el
  74.                 .dispatchEvent(new MouseEvent("click"));
  75.         }],
  76.         ["+", "Speed Up", function(ev){
  77.             if(is_comment_box(ev.target))
  78.                 return false;
  79.  
  80.             (buttons.selected || buttons.head)
  81.                 .getnext()
  82.                 .el
  83.                 .dispatchEvent(new MouseEvent("click"));
  84.         }],
  85.         ["*", "Reset Speed", function(ev){
  86.             let selbtn = buttons.head;
  87.             let result = null;
  88.  
  89.             if(is_comment_box(ev.target))
  90.                 return false;
  91.  
  92.             while(selbtn !== null && result === null)
  93.                 if(selbtn.speed === getspeed())
  94.                     result = selbtn;
  95.                 else
  96.                     selbtn = selbtn.next;
  97.  
  98.             if(result === null)
  99.                 result = buttons.head;
  100.  
  101.             result.el.dispatchEvent(new MouseEvent("click"));
  102.         }],
  103.         ["?", "Show Help", function(ev){
  104.             let infobox;
  105.  
  106.             if(is_comment_box(ev.target))
  107.                 return false;
  108.  
  109.             (infobox = Infobox(container))
  110.                 .log("Keyboard Controls (click to close)<br>");
  111.  
  112.             keyboard_controls.forEach(function([key, description]){
  113.                 infobox.log(`    [${key}]  ${description}<br>`);
  114.             });
  115.         }]
  116.     ];
  117.  
  118.     const container = (function(){
  119.         let div = document.createElement("div");
  120.         let prev_node = null;
  121.  
  122.         div.className = "vsb-container";
  123.         div.style.marginBottom = "0px";
  124.         div.style.paddingBottom = "0px";
  125.         div.style.float = "left";
  126.         div.appendChild(document.createTextNode(LABEL_TEXT));
  127.  
  128.         BUTTON_TEMPLATES.forEach(function(button){
  129.             let speedButton = SpeedButton(...button, div);
  130.  
  131.             if(buttons.head === null)
  132.                 buttons.head = speedButton;
  133.  
  134.             if(prev_node !== null){
  135.                 speedButton.prev = prev_node;
  136.                 prev_node.next = speedButton;
  137.             }
  138.  
  139.             prev_node = speedButton;
  140.  
  141.             if(speedButton.speed == getspeed())
  142.                 speedButton.select();
  143.         });
  144.  
  145.         return div;
  146.     })();
  147.  
  148.     function is_comment_box(el){
  149.         const candidate = [
  150.             ".comment-simplebox-text",
  151.             "textarea"
  152.         ].map(c => document.querySelector(c))
  153.          .find(el => el !== null);
  154.  
  155.         if(candidate === null){
  156.             logvsb("video_speed_buttons::is_comment_box", "no candidate for comment box. Assuming false.");
  157.             return 0;
  158.         }
  159.  
  160.         return el === candidate;
  161.     }
  162.  
  163.     function Infobox(parent){
  164.         let el = document.createElement("pre");
  165.  
  166.         el.style.font = "1em monospace";
  167.         el.style.borderTop = "1px solid #ccc";
  168.         el.style.marginTop = "10px";
  169.         el.style.paddingTop = "10px";
  170.  
  171.         el.addEventListener("click", function(){
  172.             parent.removeChild(el);
  173.         });
  174.  
  175.         parent.appendChild(el);
  176.  
  177.         function log(msg){
  178.             el.innerHTML += msg;
  179.         }
  180.  
  181.         return {
  182.             el,
  183.             log
  184.         };
  185.     }
  186.  
  187.     function setPlaybackRate(el, rate){
  188.         if(el)
  189.             el.playbackRate = rate;
  190.         else
  191.             logvsb("video_speed_buttons::setPlaybackRate", "video element is null or undefined", 1);
  192.     }
  193.  
  194.     function SpeedButtonLabel(text){
  195.         let el = document.createElement("span");
  196.  
  197.         el.innerHTML = text;
  198.         el.style.marginRight = "10px";
  199.         el.style.fontWeight = "bold";
  200.         el.style.fontSize = BUTTON_SIZE;
  201.         el.style.color = COLOR_NORMAL;
  202.  
  203.         return el;
  204.     }
  205.  
  206.     function SpeedButton(text, speed, parent){
  207.         let el = SpeedButtonLabel(text);
  208.         let self;
  209.  
  210.         el.style.cursor = "pointer";
  211.  
  212.         el.addEventListener("click", function(){
  213.             setPlaybackRate(video_el, speed);
  214.             self.select();
  215.         });
  216.  
  217.         parent.appendChild(el);
  218.  
  219.         function select(){
  220.             if(buttons.last !== null)
  221.                 buttons.last.el.style.color = COLOR_NORMAL;
  222.  
  223.             buttons.last = self;
  224.             buttons.selected = self;
  225.             el.style.color = COLOR_SELECTED;
  226.         }
  227.  
  228.         function getprev(){
  229.             if(self.prev === null)
  230.                 return self;
  231.  
  232.             return buttons.selected = self.prev;
  233.         }
  234.  
  235.         function getnext(){
  236.             if(self.next === null)
  237.                 return self;
  238.  
  239.             return buttons.selected = self.next;
  240.         }
  241.  
  242.         return self = {
  243.             el,
  244.             text,
  245.             speed,
  246.             prev:  null,
  247.             next:  null,
  248.             select,
  249.             getprev,
  250.             getnext
  251.         };
  252.     }
  253.  
  254.     function kill(){
  255.         anchor.removeChild(container);
  256.         document.body.removeEventListener("keydown", ev_keyboard);
  257.     }
  258.  
  259.     function set_video_el(new_video_el){
  260.         video_el = new_video_el;
  261.     }
  262.  
  263.     function ev_keyboard(ev){
  264.         let match = keyboard_controls.find(([key, unused, callback]) => key === ev.key);
  265.         let callback = (match || {2: ()=>null})[2];
  266.  
  267.         callback(ev);
  268.     }
  269.  
  270.     setPlaybackRate(video_el, getspeed());
  271.     anchor.insertBefore(container, anchor.firstChild);
  272.     document.body.addEventListener("keydown", ev_keyboard);
  273.  
  274.     return {
  275.         controls: keyboard_controls,
  276.         buttons,
  277.         kill,
  278.         SpeedButton,
  279.         Infobox,
  280.         setPlaybackRate,
  281.         is_comment_box,
  282.         set_video_el,
  283.         ALLOW_EXTERNAL_ACCESS,
  284.     };
  285. }
  286.  
  287. video_speed_buttons.from_query = function(anchor_q, video_q){
  288.     return video_speed_buttons(
  289.             document.querySelector(anchor_q),
  290.             document.querySelector(video_q));
  291. }
  292.  
  293. // Multi-purpose Loader (defaults to floating on top right)
  294. const loader_data = {
  295.     container_candidates: [
  296.         // YouTube
  297.         //"div.ytp-progress-bar-padding",
  298.         //"ytp-chrome-controls",
  299.         "div.ytp-right-controls",
  300.         //"div#container.ytd-video-primary-info-renderer",
  301.         //"div.ytd-video-primary-info-renderer",
  302.         /*"div#watch-header",
  303.         "div#watch7-headline",
  304.         "div#watch-headline-title",
  305.         "div#ytp-title",*/
  306.         // Vimeo
  307.         ".clip_info-wrapper",
  308.     ],
  309.  
  310.     css_div: [
  311.         // "position:    fixed",
  312.         // "top:         0",
  313.         // "right:       0",
  314.         // "zIndex:      100000",
  315.         // "background:  rgba(0, 0, 0, 0.8)",
  316.         "background:  rgba(0, 0, 0, 0)",
  317.         "color:       #eeeeee"
  318.         // "padding:     10px"
  319.     ].map(rule => rule.split(/: */)),
  320.  
  321.     css_vsb_container: [
  322.         "borderBottom:    none",
  323.         "marginBottom:    0",
  324.         "paddingBottom:   0",
  325.     ].map(rule => rule.split(/: */))
  326. };
  327.  
  328. function logvsb(where, msg, lvl = 0){
  329.     let logf = (["info", "error"])[lvl];
  330.  
  331.     console[logf](`[vsb::${where}] ${msg}`);
  332. }
  333.  
  334. function loader_loop(){
  335.     let vsbc = () => document.querySelector(".vsb-container");
  336.     let candidate;
  337.     let default_candidate;
  338.     let vsb_handle;
  339.  
  340.     if(vsbc() !== null)
  341.         return;
  342.  
  343.     candidate = loader_data
  344.         .container_candidates
  345.         .map(candidate => document.querySelector(candidate))
  346.         .find(candidate => candidate !== null);
  347.  
  348.     default_candidate = (function(){
  349.         let el = document.createElement("div");
  350.  
  351.         loader_data.css_div.forEach(function([name, value]){
  352.             el.style[name] = value; });
  353.  
  354.         return el;
  355.     }());
  356.  
  357.     vsb_handle = video_speed_buttons(candidate || default_candidate, document.querySelector("video"));
  358.  
  359.     if(candidate === null){
  360.         logvsb("loader_loop", "no candidates for title section. Defaulting to top of page.");
  361.         document.body.appendChild(default_candidate);
  362.  
  363.         loader_data.css_vsb_container.forEach(function([name, value]){
  364.             vsbc().style[name] = value;
  365.         });
  366.     }
  367.  
  368.     if(vsb_handle.ALLOW_EXTERNAL_ACCESS)
  369.         window.vsb = vsb_handle;
  370. }
  371.  
  372. setInterval(function(){
  373.     if(document.readyState === "complete")
  374.         setTimeout(loader_loop, 1000);
  375. }, 1000); // Blame YouTube for this
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement