Improved Speed Slider For YouTube

Add Speed Slider to Youtube Player Settings

// ==UserScript==
// @name         Improved Speed Slider For YouTube
// @namespace    improved_youtube_player_speed_slider
// @version      1.0
// @description  Add Speed Slider to Youtube Player Settings
// @author       Łukasz, pabli
// @match        https://*.youtube.com/*
// @grant        none
// ==/UserScript==
var yts_build_timeout = 500;
var yts_remove_timeout = 1000;

var yts_el_menu = null;
var yts_el_slider_label = null;
var yts_el_slider_icon = null;
var yts_el_slider_check = null;
var yts_el_slider = null;
var yts_el_player = null;

var yts_event_em_speed = false;
var yts_event_player = false;

var yts_r = 'yts_r';
var yts_s = 'yts_s';


/*************************************
 *          INIT                     *
 ************************************/
function ytsInit() {
    $yts.event(document, "spfdone", function () {
        ytsInitPlayer();
    });
    ytsReopenMenu();
    ytsBuildApp();
}

function ytsBuildApp() {

    yts_el_menu = $yts.get('.ytp-panel-menu');
    if (yts_el_menu !== null) {
        setTimeout(ytsRemoveDefaultSpeed, yts_remove_timeout);
        ytsInitSlider();
        ytsInitMenu();
        ytsInitPlayer();
    }
    else {
        setTimeout(ytsBuildApp, yts_build_timeout);
    }
}


/*************************************
 *          MENU                    *
 ************************************/

function ytsInitMenu() {

    var speedMenu = $yts.new('div', {'className': 'ytp-menuitem', id: 'yts-menu'});
    var right = $yts.new('div', {'className': 'ytp-menuitem-content'});
	var cssMenu = $yts.new('style', {'type': 'text/css'});
	var css =`
.ytp-menuitem-toggle-checkbox[type="checkbox"]{
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
	outline: none;
	cursor: pointer;
}
.ytp-menuitem-toggle-checkbox:checked[type="checkbox"]
{
	background: #f00;
}
.ytp-menuitem-toggle-checkbox:checked[type="checkbox"]:after
{
	left: 20px;
	background-color: #fff;
}
.ytp-menuitem-slider {
	-webkit-appearance: none;
	vertical-align: middle;
	outline: none;
	border: none;
	padding: 0;
	background: none;
}
.ytp-menuitem-slider::-webkit-slider-runnable-track {
	background-color: rgba(255, 255, 255, .5);
	height: 6px;
	border-radius: 3px;
	border: 1px solid transparent;
}
.ytp-menuitem-slider[disabled]::-webkit-slider-runnable-track {
	border: 1px solid rgba(255, 255, 255, .5);
	background-color: transparent;
	opacity: 0.4;
}
.ytp-menuitem-slider::-moz-range-track {
	background-color: rgba(255, 255, 255, .5);
	height: 6px;
	border-radius: 3px;
	border: none;
}
.ytp-menuitem-slider::-ms-track {
	color: transparent;
	border: none;
	background: none;
	height: 6px;
}
.ytp-menuitem-slider::-ms-fill-lower {
	background-color: rgba(255, 255, 255, .5);
	border-radius: 3px;
}
.ytp-menuitem-slider::-ms-fill-upper {
	background-color: rgba(255, 255, 255, .5);
	border-radius: 3px;
}
.ytp-menuitem-slider::-ms-tooltip {
	display: none;
}
.ytp-menuitem-slider::-moz-range-thumb {
	border-radius: 20px;
	height: 14px;
	width: 14px;
	border: none;
	background: none;
	background-color: #fff;
	box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.6);
	-moz-transition: all .08s cubic-bezier(0.4, 0.0, 1, 1);
}
.ytp-menuitem-slider:active::-moz-range-thumb {
	outline: none;
}
.ytp-menuitem-slider::-webkit-slider-thumb {
	-webkit-appearance: none !important;
	border-radius: 100%;
	background-color: #fff;
	height: 14px;
	width: 14px;
	margin-top: -5px;
	box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.6);
	-moz-transition: all .08s cubic-bezier(0.4, 0.0, 1, 1);
	-webkit-transition: all .08s cubic-bezier(0.4, 0.0, 1, 1);
}
.ytp-menuitem-slider:active::-webkit-slider-thumb {
	outline: none;
}
.ytp-menuitem-slider::-ms-thumb {
	border-radius: 100%;
	background-color: #fff;
	height: 14px;
	width: 14px;
	border: none;
}
.ytp-menuitem-slider:active::-ms-thumb {
	border: none;
}
`;
	cssMenu.innerHTML = css;
    right.appendChild(yts_el_slider_check);
    right.appendChild(yts_el_slider);
    right.appendChild(cssMenu);
    speedMenu.appendChild(yts_el_slider_icon);
    speedMenu.appendChild(yts_el_slider_label);
    speedMenu.appendChild(right);
    yts_el_menu.appendChild(speedMenu);
}

function ytsRemoveDefaultSpeed() {
    var switchers = $yts.getOpt(".ytp-menuitem", {role: 'menuitemcheckbox'});
    var toRemove = null;

    if (!ytsPlayerHasClass('ad-interrupting') && switchers && switchers.length && !yts_event_em_speed) {
        toRemove = switchers[switchers.length - 1].nextElementSibling;
        if (toRemove && toRemove.id !== 'yts-menu') {
            $yts.style(toRemove, 'display', 'none');
            yts_event_em_speed = true;
        }
    }
}

function ytsReopenMenu() {
    var settings_button = $yts.get(".ytp-settings-button");
    settings_button && settings_button.click();
    settings_button && settings_button.click();
}


/*************************************
 *          SLIDER                   *
 ************************************/

function ytsInitSlider() {
    var rem = ytsParam(yts_r);
    var speed = ytsParam(yts_s) || 1;
    speed = rem ? speed : 1;

	yts_el_slider_icon = $yts.new('div', {'className': 'ytp-menuitem-icon'});//pabli
    yts_el_slider_label = $yts.new('div', {'className': 'ytp-menuitem-label'});
    yts_el_slider_check = $yts.new('input', {
        'type': 'checkbox',
        'title': 'Remember speed',
		'className': 'ytp-menuitem-toggle-checkbox'
    });

    yts_el_slider = $yts.new('input', {
		'className': 'ytp-menuitem-slider',
        'type': 'range',
        'min': 0.1,
        'max': 5,
        'step': 0.1,
        'value': speed,
        style: {
            'width': 'calc(100% - 60px)',
            'margin': '0 5px',
            'padding': '0',
			'vertical-align': 'text-bottom'
        }
    });
    if(rem){
        yts_el_slider_check.checked = true;
    }
    $yts.event(yts_el_slider, 'change', ytsChangeSlider);
    $yts.event(yts_el_slider_check, 'change', ytsChangeRemember);
    $yts.event(yts_el_slider, 'input', ytsChangeSlider);
    $yts.event(yts_el_slider, 'wheel', ytsWheelSlider);

    ytsUpdateSliderLabel(speed);
}


function ytsWheelSlider(event) {
    var val = parseFloat(event.target.value) + (event.wheelDelta > 0 ? 0.1 : -0.1);
    val = val < 0.1 ? 0.1 : (val > 5 ? 5 : val);
    if (event.target.value !== val) {
        event.target.value = val;
        ytsUpdateSliderLabel(val);
    }
    event.preventDefault();
}

function ytsChangeSlider(event) {
    ytsUpdateSliderLabel(event.target.value);
}

function ytsChangeRemember() {
    ytsParam(yts_r, ytsParam(yts_r) ? 0 : 1);
}


function ytsUpdateSliderLabel(val) {
    ytsSetPlayerDuration(val);
    yts_el_slider_label.innerHTML = 'Speed (' + parseFloat(val).toFixed(1) + ')';
}


/*************************************
 *          PLAYER                   *
 ************************************/

function ytsInitPlayer() {
    yts_el_player = $yts.get('.html5-main-video');
    ytsObservePlayer();
    if (ytsParam(yts_s) && ytsParam(yts_r)) {
        ytsSetPlayerDuration(ytsParam(yts_s));
        ytsUpdateSliderLabel(ytsParam(yts_s));
    }

}

function ytsPlayerHasClass (cls) {
    ytsInitPlayer();
    return yts_el_player && yts_el_player.classList.contains(cls);
}

function ytsSetPlayerDuration(value) {
    ytsParam(yts_s, value);
    if (yts_el_player) {
        yts_el_player.playbackRate = value;
    }
}

function ytsObservePlayer() {
    if (!yts_event_player) {
        ytsObserver(yts_el_player.parentNode.parentNode, function (mutation) {
            if (/html5-video-player/.test(mutation.target.className) && !yts_event_em_speed) {
                ytsRemoveDefaultSpeed();
            }
        });
        yts_event_player = true;
    }
}


/************************************
 *                DOM                *
 ************************************/
$yts = {
    'event': function (obj, event, callback) {
        obj.addEventListener(event, callback);
    },
    'new': function (tag, option) {
        var element = document.createElement(tag);
        for (var param in option) {
            if (param === 'data' || param === 'style' || param === 'attr') {
                for (var data in option[param]) {
                    $yts[param](element, data, option[param][data]);
                }
            }
            else {
                element[param] = option[param];
            }
        }
        return element;
    },
    'get': function (tselector, all) {
        all = all || false;
        var type = tselector.substring(0, 1);
        var selector = tselector.substring(1);
        var elements;
        if (type === "#") {
            return document.getElementById(selector);
        }
        else if (type === ".") {
            elements = document.getElementsByClassName(selector);
        }
        else {
            elements = document.querySelectorAll(tselector);
        }

        if (all) {
            return elements;
        }
        else {
            return elements.length ? elements[0] : null;
        }
    },
    'data': function (elem, key, val) {
        key = key.replace(/-(\w)/gi, function (x) {
            return x.charAt(1).toUpperCase();
        });
        if (typeof val !== 'undefined') {
            elem.dataset[key] = val;
        }
        return elem.dataset[key];

    },
    'style': function (elem, key, val, priority) {
        priority = priority || '';
        if (typeof val !== 'undefined') {
            elem.style.setProperty(key, val, priority);
        }
        return elem.style.getPropertyValue(key);

    },
    'attr': function (elem, key, val) {
        if (typeof val !== 'undefined') {
            elem.setAttribute(key, val);
        }
        return elem.getAttribute(key);

    },
    'getOpt': function (selector, option) {
        var el = $yts.get(selector, true);
        var pass = [];
        var correct;
        for (var i = 0; i < el.length; i++) {
            correct = true;
            for (var prop in option) {
                if (!$yts.has(el[i], prop, option[prop])) {
                    correct = false;
                    break;
                }
            }
            if (correct) {
                pass.push(el[i]);
            }
        }
        return pass;
    },
    'has': function (elem, key, val) {
        if (elem.hasAttribute(key)) {
            var attr = elem.getAttribute(key);
            if (val !== null) {
                return attr == val;
            }
            return true;
        }
        return false;
    }
};

/*************************************
 *          OBSERVER                 *
 ************************************/
function ytsObserver(element, callback) {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    if (MutationObserver) {
        var obs = new MutationObserver(function (mutations) {
            callback(mutations[0]);
        });

        obs.observe(element, {
            childList: true,
            subtree: true,
            attributes: true,
            characterData: true,
            attributeOldValue: true,
            characterDataOldValue: true
        });
    }
}

function ytsParam(key, val) {
    if (typeof val !== 'undefined') {
        localStorage.setItem(key, val);
    }
    return parseFloat(localStorage.getItem(key));
}

ytsInit();