Youtube Player Speed Slider

Add Speed Slider to Youtube Player Settings

当前为 2016-11-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Player Speed Slider
  3. // @namespace youtube_player_speed_slider
  4. // @version 0.1.3.1
  5. // @description Add Speed Slider to Youtube Player Settings
  6. // @author Łukasz and Witold
  7. // @match https://*.youtube.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. var yts = {};
  12.  
  13. yts.option = {
  14. timeoutBuild: 500,
  15. timeoutRemove: 1000,
  16. lastClick: 0
  17. };
  18.  
  19. yts.elements = {
  20. menu : null,
  21. speedMenu: null,
  22. slider: null,
  23. sliderLabel: null
  24. };
  25.  
  26. yts.modules = [
  27. 'setting',
  28. 'i18'
  29. ];
  30.  
  31. /*************************************
  32. * TRANSLATION *
  33. ************************************/
  34. yts.i18 = {};
  35.  
  36. yts.i18.dic = {
  37. pl: {
  38. 'Speed': 'Szybkość',
  39. 'Annotation': 'Adnotacje',
  40. 'Settings': 'Ustawienia'
  41. },
  42. en: {
  43. 'Speed': 'Speed',
  44. 'Annotation': 'Annotation',
  45. 'Settings': 'Settings'
  46. },
  47. de: {
  48. 'Speed' : 'Geschwindigkeit',
  49. 'Annotation': '',
  50. 'Settings': 'Einstellungen'
  51. },
  52. fr:{
  53. 'Speed':'Vitesse',
  54. 'Annotation':'',
  55. 'Settings':'Paramètres'
  56. }
  57. };
  58.  
  59. yts.i18.opt = {
  60. lang: 'en',
  61. default: 'en',
  62. map:{
  63. pl: 'pl',
  64. pl_pl: 'pl',
  65. en: 'en',
  66. en_gb: 'en',
  67. de: 'de',
  68. fr: 'fr'
  69. }
  70. };
  71.  
  72. yts.i18.init = function () {
  73. yts.i18.opt.lang = yts.i18.getLang();
  74. };
  75.  
  76. yts.i18.getLang = function () {
  77. var lang = yts.i18.filter(yts.setting.get('lang'));
  78. if(lang !== '' && yts.i18.isAllow(lang)){
  79. return yts.i18.map(lang);
  80. }
  81.  
  82. lang = yts.i18.filter(yts.dom.get('html').getAttribute('lang'));
  83. if(lang !== '' && yts.i18.isAllow(lang)){
  84. return yts.i18.map(lang);
  85. }
  86.  
  87. return yts.i18.opt.default;
  88. };
  89.  
  90. yts.i18.isAllow = function (lang) {
  91. return yts.i18.opt.map.hasOwnProperty(lang);
  92. };
  93.  
  94. yts.i18.t = function (pattern) {
  95. if(yts.i18.dic[yts.i18.opt.lang].hasOwnProperty(pattern)){
  96. return yts.i18.dic[yts.i18.opt.lang][pattern];
  97. }
  98. else {
  99. return pattern;
  100. }
  101. };
  102.  
  103. yts.i18.filter = function (lang) {
  104. return lang ? lang.replace('-', '_').toLowerCase() : '';
  105. };
  106.  
  107. yts.i18.map = function (lang) {
  108. return yts.i18.opt.map[lang];
  109. };
  110.  
  111.  
  112. /*************************************
  113. * INIT *
  114. ************************************/
  115. yts.init = function () {
  116. yts.player.update();
  117. yts.modules.forEach(function (module) {
  118. yts[module].init();
  119. });
  120. yts.option.lastClick = (new Date()).getTime();
  121. yts.dom.event(document, "spfdone", yts.player.update);
  122. yts.menu.reopen();
  123. yts.buildApp();
  124. };
  125.  
  126. yts.buildApp = function () {
  127. yts.elements.menu = yts.dom.get('.ytp-panel-menu');
  128. if (yts.elements.menu !== null) {
  129. setTimeout(yts.menu.removeDefaultSpeed, yts.option.timeoutRemove);
  130.  
  131. yts.menu.build();
  132. yts.annot.init();
  133. }
  134. else {
  135. setTimeout(yts.buildApp, yts.option.timeoutBuild);
  136. }
  137. };
  138.  
  139.  
  140. /*************************************
  141. * MENU *
  142. ************************************/
  143. yts.menu = {};
  144.  
  145. yts.menu.build = function () {
  146. yts.slider.build();
  147.  
  148. var speedMenu = yts.dom.new('div', {
  149. 'className': 'ytp-menuitem'
  150. });
  151.  
  152. var right = yts.dom.new('div', {
  153. 'className': 'ytp-menuitem-content'
  154. });
  155.  
  156. yts.elements.sliderLabel = yts.dom.new('div', {
  157. 'className': 'ytp-menuitem-label'
  158. });
  159.  
  160. yts.slider.updateLabel(1);
  161. speedMenu.appendChild(yts.elements.sliderLabel);
  162. speedMenu.appendChild(right.appendChild(yts.elements.slider));
  163. yts.elements.menu.appendChild(speedMenu);
  164. yts.menu.event();
  165. };
  166.  
  167. yts.menu.event = function () {
  168. yts.elements.menu.addEventListener('click', yts.menu.click);
  169. };
  170.  
  171. yts.menu.removeDefaultSpeed = function(){
  172. var switchers = yts.dom.getOpt(".ytp-menuitem", {role:'menuitemcheckbox'});
  173. switchers[switchers.length-1].nextElementSibling.style.display = 'none';
  174. };
  175.  
  176. yts.menu.reopen = function () {
  177. var settings_button = yts.dom.get(".ytp-settings-button");
  178. settings_button.click();
  179. settings_button.click();
  180. };
  181.  
  182. yts.menu.click = function () {
  183. yts.option.lastClick = (new Date()).getTime();
  184. };
  185.  
  186. /*************************************
  187. * SLIDER *
  188. ************************************/
  189. yts.slider = {};
  190.  
  191. yts.slider.build = function () {
  192. yts.elements.slider = yts.dom.new('input', {
  193. 'className': '',
  194. 'type': 'range',
  195. 'min': 0.5,
  196. 'max': 4,
  197. 'step': 0.1,
  198. 'value': 1
  199. });
  200. yts.dom.event(yts.elements.slider, 'change', yts.slider.change);
  201. yts.dom.event(yts.elements.slider, 'input', yts.slider.move);
  202. };
  203.  
  204. yts.slider.move = function (event) {
  205. yts.slider.updateLabel(event.target.value);
  206. };
  207.  
  208. yts.slider.change = function (event) {
  209. yts.player.duration(event.target.value);
  210. };
  211.  
  212. yts.slider.updateLabel = function (val) {
  213. yts.elements.sliderLabel.innerHTML = yts.i18.t('Speed') + ': ' + parseFloat(val).toFixed(1);
  214. };
  215.  
  216.  
  217. /*************************************
  218. * PLAYER *
  219. ************************************/
  220. yts.player = {};
  221.  
  222. yts.player.update = function(){
  223. yts.elements.player = yts.dom.get('.html5-main-video');
  224.  
  225. };
  226. yts.player.duration = function(value){
  227. yts.elements.player.playbackRate = value;
  228. };
  229.  
  230.  
  231. /*************************************
  232. * ANNOTATION *
  233. ************************************/
  234. yts.annot = {};
  235. yts.annot.init = function(){
  236. if(yts.setting.get('annot') !== 'on'){
  237. yts.observer.init(yts.elements.menu, yts.annot.change);
  238. }
  239. };
  240.  
  241. yts.annot.change = function (mutation) {
  242. if(mutation.type == "attributes" && mutation.target.getAttribute('role')=="menuitemcheckbox"){
  243. yts.annot.switchOff();
  244. }
  245. };
  246.  
  247. yts.annot.switchOff = function(){
  248. var switchers = yts.dom.getOpt(".ytp-menuitem", {role:'menuitemcheckbox'});
  249. if(switchers.length == 2){
  250. setTimeout(function (switcher) {
  251. if((new Date()).getTime() - yts.option.lastClick > 1000 &&
  252. switcher.getAttribute('aria-checked') == "true"){
  253. switcher.click();
  254. }
  255. },
  256. 500,
  257. switchers[1]
  258. );
  259. }
  260. };
  261.  
  262. yts.annot.switchOffAlways = function(){
  263. yts.setting.set('annot', 'off');
  264. yts.annot.switchOff();
  265. };
  266.  
  267. yts.annot.switchOn = function(){
  268. yts.setting.set('annot', 'on');
  269. };
  270.  
  271.  
  272.  
  273. /*************************************
  274. * COOKIE *
  275. ************************************/
  276. yts.cookie ={};
  277.  
  278. yts.cookie.set = function (name, value, days) {
  279. days = days || 366;
  280. var d = new Date();
  281. d.setTime(d.getTime() + (days*24*60*60*1000));
  282. var expires = "expires="+ d.toUTCString();
  283. document.cookie = name + "=" + value + ";" + expires + ";path=/";
  284. };
  285.  
  286. yts.cookie.get = function (name) {
  287. name += "=";
  288. var ca = document.cookie.split(';');
  289. for(var i = 0; i <ca.length; i++) {
  290. var c = ca[i];
  291. while (c.charAt(0)==' ') {
  292. c = c.substring(1);
  293. }
  294. if (c.indexOf(name) == 0) {
  295. return c.substring(name.length,c.length);
  296. }
  297. }
  298. return "";
  299. };
  300.  
  301.  
  302. /************************************
  303. * DOM *
  304. ************************************/
  305. yts.dom = {};
  306.  
  307. yts.dom.event = function (obj, event, callback) {
  308. obj.addEventListener(event, callback);
  309. };
  310.  
  311. yts.dom.new = function (tag, option) {
  312. var element = document.createElement(tag);
  313. for (var param in option) {
  314. element[param] = option[param];
  315. }
  316. return element;
  317. };
  318.  
  319. yts.dom.get = function (tselector, all) {
  320. all = all || false;
  321. var type = tselector.substring(0, 1);
  322. var selector = tselector.substring(1);
  323. var elements;
  324. if (type == "#") {
  325. return document.getElementById(selector);
  326. }
  327. else if (type == ".") {
  328. elements = document.getElementsByClassName(selector);
  329. }
  330. else{
  331. elements = document.querySelectorAll(tselector);
  332. }
  333.  
  334. if (all) {
  335. return elements;
  336. }
  337. else {
  338. return elements.length ? elements[0] : null;
  339. }
  340. };
  341.  
  342. yts.dom.getOpt = function(selector, option){
  343. var el = yts.dom.get(selector, true);
  344. var pass = [];
  345. var correct;
  346. for(var i =0; i< el.length; i++){
  347. correct = true;
  348. for(var prop in option){
  349. if(!yts.dom.has(el[i], prop, option[prop])){
  350. correct = false;
  351. break;
  352. }
  353. }
  354. if(correct){
  355. pass.push(el[i]);
  356. }
  357. }
  358. return pass;
  359. };
  360.  
  361. yts.dom.has = function (elem, key, val) {
  362. if(elem.hasAttribute(key)){
  363. var attr = elem.getAttribute(key);
  364. if(val !== null){
  365. return attr == val;
  366. }
  367. return true;
  368. }
  369. return false;
  370. };
  371.  
  372.  
  373. /*************************************
  374. * MENU OBSERVER *
  375. ************************************/
  376. yts.observer= {};
  377.  
  378. yts.observer.obj = null;
  379.  
  380. yts.observer.get = function () {
  381. return window.MutationObserver || window.WebKitMutationObserver;
  382. };
  383.  
  384. yts.observer.init = function (element, callback) {
  385. var MutationObserver = yts.observer.get();
  386. if( MutationObserver ){
  387. var obs = new MutationObserver(function(mutations, observer){
  388. callback(mutations[0]);
  389. });
  390.  
  391. obs.observe( element, {
  392. childList: true,
  393. subtree: true,
  394. attributes:true,
  395. characterData:true,
  396. attributeOldValue:true,
  397. characterDataOldValue:true
  398. });
  399. }
  400. };
  401.  
  402.  
  403. /*************************************
  404. * SETTINGS *
  405. ************************************/
  406. yts.setting = {};
  407.  
  408. yts.setting.settings = null;
  409. yts.setting.init = function(){
  410. var cookie = yts.cookie.get('yts_s');
  411. yts.setting.settings = JSON.parse(cookie == '' ? '{}' : cookie);
  412. };
  413. yts.setting.set = function(key, val){
  414. yts.setting.settings[key] = val;
  415. yts.cookie.set('yts_s', JSON.stringify(yts.setting.settings));
  416. };
  417. yts.setting.get = function(kay){
  418. return yts.setting.settings.hasOwnProperty(kay) ?
  419. yts.setting.settings[kay] : null;
  420. };
  421.  
  422. yts.init();