// ==UserScript==
// @name Youtube Player Speed Slider
// @namespace youtube_player_speed_slider
// @version 0.2.0
// @description Add Speed Slider to Youtube Player Settings
// @author Łukasz
// @match https://*.youtube.com/*watch*
// @match https://*.youtube.com/embed*
// @grant none
// ==/UserScript==
var yts = {};
yts.option = {
timeoutBuild: 500,
timeoutRemove: 1000,
lastClick: 0
};
yts.elements = {
menu : null,
speedMenu: null,
slider: null,
sliderLabel: null,
annot: null
};
yts.event = {
speedRemove: false,
playerObserve: false,
addEnd: true
};
yts.modules = {
'before':[
'click',
'setting',
'i18n',
'ad'
],
'after':[
'slider',
'menu',
'annot',
'player',
'appmenu'
],
'spfdone':[
'appmenu',
'player',
'annot'
],
'afterAd':[
''
]
};
/*************************************
* TRANSLATION *
************************************/
yts.i18n = {};
yts.i18n.dic = {
pl: {
'Speed': 'Szybkość',
'Settings': 'Ustawienia',
'Language': 'Język',
'Switch off advertisement': 'Wyłączaj reklamy',
'Hide annotation': 'Ukryj adnotacje',
'Remember speed': 'Pamiętaj szybkość'
},
de: {
'Speed' : 'Geschwindigkeit',
'Settings': 'Einstellungen',
'Language': 'Sprache',
'Switch off advertisement': 'Abschalten Werbung',
'Hide annotation': 'Ausblenden Anmerkungen',
'Remember speed': 'Erinnern Geschwindigkeit'
},
fr:{
'Speed':'Vitesse',
'Settings':'Paramètres',
'Language': 'La langue',
'Switch off advertisement': 'Désactiver la publicité',
'Hide annotation': 'Cacher les annotations',
'Remember speed': 'Rappelez-vous la vitesse'
},
en:{}
};
yts.i18n.opt = {
lang: 'en',
default: 'en',
map:{
pl: 'pl',
pl_pl: 'pl',
en: 'en',
en_gb: 'en',
de: 'de',
fr: 'fr'
}
};
yts.i18n.init = function () {
yts.i18n.opt.lang = yts.i18n.getLang();
yts.setting.change('lang', function (val) {
yts.i18n.opt.lang = yts.i18n.getLang();
});
};
yts.i18n.getLang = function () {
var lang = yts.i18n.filter(yts.setting.get('lang'));
if(lang !== '' && yts.i18n.isAllow(lang)){
return yts.i18n.map(lang);
}
lang = yts.i18n.filter(yts.$.get('html').getAttribute('lang'));
if(lang !== '' && yts.i18n.isAllow(lang)){
return yts.i18n.map(lang);
}
return yts.i18n.opt.default;
};
yts.i18n.isAllow = function (lang) {
return yts.i18n.opt.map.hasOwnProperty(lang);
};
yts.i18n.t = function (pattern) {
if(yts.i18n.dic.hasOwnProperty(yts.i18n.opt.lang) &&
yts.i18n.dic[yts.i18n.opt.lang].hasOwnProperty(pattern)){
return yts.i18n.dic[yts.i18n.opt.lang][pattern];
}
else {
return pattern;
}
};
yts.i18n.filter = function (lang) {
return lang ? lang.replace('-', '_').toLowerCase() : '';
};
yts.i18n.map = function (lang) {
return yts.i18n.opt.map[lang]
};
yts.i18n.all = function () {
var ret=[];
for(var p in yts.i18n.dic) if(yts.i18n.dic.hasOwnProperty(p)) ret.push(p);
return ret;
};
/*************************************
* INIT *
************************************/
yts.init = function () {
yts.modules.before.forEach(function (module) {
yts[module].init();
});
yts.$.event(document, "spfdone", function () {
yts.modules.spfdone.forEach(function (module) {
yts[module].init();
});
});
yts.option.lastClick = (new Date()).getTime();
yts.menu.reopen();
yts.buildApp();
};
yts.buildApp = function () {
yts.elements.menu = yts.$.get('.ytp-panel-menu');
if (yts.elements.menu !== null) {
setTimeout(yts.menu.removeDefaultSpeed, yts.option.timeoutRemove);
yts.modules.after.forEach(function (module) {
yts[module].init();
});
}
else {
setTimeout(yts.buildApp, yts.option.timeoutBuild);
}
};
/*************************************
* MENU *
************************************/
yts.menu = {};
yts.menu.init = function () {
var speedMenu = yts.$.new('div', {'className': 'ytp-menuitem', id:'yts-menu'});
var right = yts.$.new('div', {'className': 'ytp-menuitem-content'});
speedMenu.appendChild(yts.elements.sliderLabel);
speedMenu.appendChild(right.appendChild(yts.elements.slider));
yts.elements.menu.appendChild(speedMenu);
yts.menu.event();
};
yts.menu.event = function () {
yts.elements.menu.addEventListener('click', yts.menu.click);
};
yts.menu.removeDefaultSpeed = function(){
var switchers = yts.$.getOpt(".ytp-menuitem", {role:'menuitemcheckbox'});
var toremove = null;
if(!yts.player.hasClass('ad-interrupting') &&
switchers &&
switchers.length &&
!yts.event.speedRemove
){
toremove = switchers[switchers.length-1].nextElementSibling;
if(toremove && toremove.id !== 'yts-menu'){
yts.$.style(toremove, 'display', 'none');
yts.event.speedRemove = true;
}
}
};
yts.menu.reopen = function () {
var settings_button = yts.$.get(".ytp-settings-button");
settings_button && settings_button.click();
settings_button && settings_button.click();
};
yts.menu.click = function () {
yts.option.lastClick = (new Date()).getTime();
};
/*************************************
* SLIDER *
************************************/
yts.slider = {};
yts.slider.init = function () {
var speed = yts.setting.get('speed') || 1;
speed = yts.setting.get('rem') ? speed : 1;
yts.elements.sliderLabel = yts.$.new('div', {'className': 'ytp-menuitem-label'});
yts.elements.slider = yts.$.new('input', {
'className': '',
'type': 'range',
'min': 0.5,
'max': 4,
'step': 0.1,
'value': speed,
style:{
'width': '90%',
'margin':'0 5%'
}
});
yts.$.event(yts.elements.slider, 'change', yts.slider.change);
yts.$.event(yts.elements.slider, 'input', yts.slider.move);
yts.slider.updateLabel(speed);
};
yts.slider.move = function (event) {
yts.slider.updateLabel(event.target.value);
};
yts.slider.change = function (event) {
yts.player.duration(event.target.value);
};
yts.slider.updateLabel = function (val) {
yts.elements.sliderLabel.innerHTML = yts.i18n.t('Speed') + ': ' + parseFloat(val).toFixed(1);
};
/*************************************
* PLAYER *
************************************/
yts.player = {};
yts.player.init = function(){
yts.elements.player = yts.$.get('.html5-main-video');
yts.player.observe();
if(yts.setting.get('speed') && yts.setting.get('rem')){
yts.player.duration(yts.setting.get('speed'));
yts.slider.updateLabel(yts.setting.get('speed'));
}
};
yts.player.hasClass = function (cls) {
yts.player.init();
return yts.elements.player && yts.elements.player.classList.contains(cls)
};
yts.player.duration = function(value){
yts.setting.set('speed', value);
yts.elements.player.playbackRate = value;
};
yts.player.observe = function () {
if(!yts.event.playerObserve){
yts.observer.init(yts.elements.player.parentNode.parentNode, function (mutation) {
if(/html5-video-player/.test(mutation.target.className) && !yts.event.speedRemove) {
yts.menu.removeDefaultSpeed();
}
});
yts.event.playerObserve = true;
}
};
/*************************************
* ANNOTATION *
************************************/
yts.annot = {};
yts.annot.init = function(){
yts.elements.annot = yts.$.get('.video-annotations');
if(yts.setting.get('annot')){
yts.annot.switch("off");
}
yts.setting.change('annot', function (val) {
if(val){
yts.annot.switch("off");
}
else{
yts.annot.switch("on");
}
});
};
yts.annot.change = function (mutation) {
if(mutation.type == "attributes" && mutation.target.getAttribute('role')=="menuitemcheckbox"){
yts.annot.switch("off");
}
};
yts.annot.switch = function(type){
if(yts.elements.annot && type == 'off'){
yts.$.style(yts.elements.annot, 'display', 'none', 'important');
}
else if(yts.elements.annot && type == 'on'){
yts.$.style(yts.elements.annot, 'display', 'block');
}
};
/*************************************
* COOKIE *
************************************/
yts.cookie ={};
yts.cookie.set = function (name, value, option) {
var d = new Date();
option = option || {};
option.expires = option.expires || 366;
d.setTime(d.getTime() + (option.expires*24*60*60*1000));
option.expires = d.toUTCString();
option.path = option.path || '/';
var cookie = name + "=" + value + "; ";
for(var prop in option){
cookie += prop + "=" + option[prop] +"; ";
}
document.cookie = cookie;
};
yts.cookie.get = function (name) {
name += "=";
var ca = document.cookie.split(';');
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length,c.length);
}
}
return null;
};
/************************************
* DOM *
************************************/
yts.$ = {};
yts.$.event = function (obj, event, callback) {
obj.addEventListener(event, callback);
};
yts.$.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;
};
yts.$.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;
}
};
yts.$.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];
};
yts.$.style = function (elem, key, val, priority) {
priority = priority || '';
if(typeof val !== 'undefined'){
elem.style.setProperty(key, val, priority);
}
return elem.style.getPropertyValue(key);
};
yts.$.attr = function (elem, key, val) {
if(typeof val !== 'undefined'){
elem.setAttribute(key, val);
}
return elem.getAttribute(key);
};
yts.$.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;
};
yts.$.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 *
************************************/
yts.observer= {};
yts.observer.obj = null;
yts.observer.get = function () {
return window.MutationObserver || window.WebKitMutationObserver;
};
yts.observer.init = function (element, callback) {
var MutationObserver = yts.observer.get();
if( MutationObserver ){
var obs = new MutationObserver(function(mutations, observer){
callback(mutations[0]);
});
obs.observe( element, {
childList: true,
subtree: true,
attributes:true,
characterData:true,
attributeOldValue:true,
characterDataOldValue:true
});
}
};
/*************************************
* SETTINGS *
************************************/
yts.setting = {};
yts.setting.item = {
rem:{
label: 'Remember speed',
type: 'switcher'
},
annot:{
label: 'Hide annotation',
type: 'switcher'
},
ad:{
label: 'Switch off advertisement',
type: 'switcher'
},
lang:{
label: 'Language',
type: 'radio',
items: yts.i18n.all()
}
};
yts.setting.settings = null;
yts.setting.event = {};
yts.setting.init = function(){
var setting = localStorage.getItem('yts');
yts.setting.settings = JSON.parse(setting === null ? '{}' : setting);
};
yts.setting.set = function(key, val){
yts.setting.settings[key] = val;
if(yts.setting.event.hasOwnProperty(key)){
yts.setting.event[key](val);
}
localStorage.setItem('yts', JSON.stringify(yts.setting.settings));
};
yts.setting.get = function(kay){
return yts.setting.settings.hasOwnProperty(kay) ?
yts.setting.settings[kay] : null;
};
yts.setting.change = function(key, callback){
yts.setting.event[key] = callback;
};
/*************************************
* CLICK *
************************************/
yts.click = {};
yts.click.fun = [];
yts.click.init = function(){
yts.$.event(yts.$.get('body'), 'click', function(event){
for(var i =0; i<yts.click.fun.length; i++){
yts.click.fun[i](event);
}
})
};
yts.click.add = function(fun){
yts.click.fun.push(fun);
};
/*************************************
* ADVERTISEMENT *
************************************/
yts.ad = {};
yts.ad.init = function(){
if(yts.setting.get('ad')){
yts.ad.switch('off');
}
yts.setting.change('ad', function (val) {
if(val){
yts.ad.switch('off');
}
else{
yts.ad.switch('on');
}
});
};
yts.ad.switch = function (type) {
var ad = yts.$.get(".video-ads");
if(type == "off"){
yts.cookie.set("VISITOR_INFO1_LIVE", "oKckVSqvaGw", {domain: "youtube.com"});
ad && yts.$.style(ad, 'display', 'none', 'important');
}
else {
yts.cookie.set("VISITOR_INFO1_LIVE", "", {domain: "youtube.com", expires: -1});
ad && yts.$.style(ad, 'display', 'block');
}
};
/*************************************
* APP MENU *
************************************/
yts.appmenu = {};
yts.appmenu.init = function(){
yts.appmenu.build();
};
yts.appmenu.build = function(){
yts.appmenu.panel();
var menu = yts.$.get("#watch8-secondary-actions");
if(menu){
menu.appendChild(yts.appmenu.button());
}
};
yts.appmenu.button = function(){
var button = yts.$.new('button', {
'className':'yt-uix-button yt-uix-button-opacity action-panel-trigger yt-uix-button-toggled',
'title' : yts.i18n.t('Settings'),
'type':'button',
'innerHTML':'<span class="yt-uix-button-content">Yts</span>',
'data': {
'tooltip-text':yts.i18n.t('Settings'),
'button-toggle':'true',
'trigger-for':'action-panel-yts'
}
});
return button;
};
yts.appmenu.panel = function(){
var panel = yts.$.new('div', {
'id':'action-panel-yts',
'className':'action-panel-content',
data: {'panel-loaded':'true'}
});
var list = yts.$.new('ul', {'className':'yt-uix-kbd-nav yt-uix-kbd-nav-list'});
yts.appmenu.addSettingItems(list);
panel.appendChild(list);
var panels = yts.$.get('#watch-action-panels');
if(panels){
panels.appendChild(panel);
}
};
yts.appmenu.addSettingItems = function (list) {
for(var item in yts.setting.item){
var li = yts.$.new('li', {
'className':'ytp-menuitem',
'role':"menuitemcheckbox",
attr: {
'aria-checked': yts.setting.get(item) ? "true" : "false",
}
});
var type = yts.setting.item[item].type;
if(type === 'switcher'){
li = yts.appmenu.switcherItem(li, item);
}
if(type === 'radio'){
li = yts.appmenu.radioItem(li, item);
}
list.appendChild(li);
}
};
yts.appmenu.switcherItem = function(li, item){
var switcher = yts.$.new('div', {
'className':'ytp-menuitem-toggle-checkbox',
style: {
'background-color':'#d2d2d2',
'margin-left': '10px'
},
data: {
'name': item
},
'onclick' : function (event) {
var el = event.target;
var parent = el.parentNode;
var stat = parent.getAttribute('aria-checked');
if(stat == 'false'){
yts.$.attr(parent, 'aria-checked' , 'true');
yts.setting.set(yts.$.data(el, 'name'), true);
}
else{
yts.$.attr(parent, 'aria-checked' , 'false');
yts.setting.set(yts.$.data(el, 'name'), false);
}
}
});
var span = yts.$.new('span', {'innerHTML':yts.i18n.t(yts.setting.item[item].label)});
li.appendChild(switcher);
li.appendChild(span);
return li;
};
yts.appmenu.radioItem = function(li, item){
var list = yts.setting.item[item].items;
var checked = yts.setting.get(item);
li.appendChild(yts.$.new('div',{'innerHTML':yts.i18n.t(yts.setting.item[item].label), style:{'margin-bottom':'5px'}}));
for(var i =0; i< list.length; i++){
var label = yts.$.new('label',{style:{'margin-right':'5px'}});
var spanInput = yts.$.new('span', {
'className': 'yt-uix-form-input-radio-container'
});
var input = yts.$.new('input', {
'type': 'radio',
'name': 'yts-' + item,
'checked': list[i] == checked,
data: {
'name': item,
'val': list[i]
},
'onchange': function (event) {
var el = event.target;
yts.setting.set(yts.$.data(el, 'name'), yts.$.data(el, 'val'));
}
});
var spanCheck = yts.$.new('span', {'className': 'yt-uix-form-input-radio-element'});
var spanLabel = yts.$.new('span', {'innerHTML': list[i]});
spanInput.appendChild(input);
spanInput.appendChild(spanCheck);
label.appendChild(spanInput);
label.appendChild(spanLabel);
li.appendChild(label);
}
return li;
};
yts.init();