// ==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();