Video Speed Buttons

Add speed buttons to any HTML5 <video> element. Comes with a loader for YouTube and Vimeo

06.09.2017 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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.3
// @copyright    2017, Braden Best
// @run-at       document-end
// @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;
    }());

    if(candidate === null)
        logvsb("loader_loop", "no candidates for title section. Defaulting to top of page.");

    video_speed_buttons(candidate || default_candidate, document.querySelector("video"));

    if(!candidate)
        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