Youtube Player Speed Slider

Add Speed Slider to Youtube Player Settings

当前为 2017-10-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Player Speed Slider
  3. // @namespace youtube_player_speed_slider
  4. // @version 0.2.2
  5. // @description Add Speed Slider to Youtube Player Settings
  6. // @author Łukasz
  7. // @match https://*.youtube.com/*
  8. // @grant none
  9. // ==/UserScript==
  10. var yts_build_timeout = 500;
  11. var yts_remove_timeout = 1000;
  12.  
  13. var yts_el_menu = null;
  14. var yts_el_slider_label = null;
  15. var yts_el_slider_check = null;
  16. var yts_el_slider = null;
  17. var yts_el_player = null;
  18.  
  19. var yts_event_em_speed = false;
  20. var yts_event_player = false;
  21.  
  22. var yts_r = 'yts_r';
  23. var yts_s = 'yts_s';
  24.  
  25.  
  26. /*************************************
  27. * INIT *
  28. ************************************/
  29. function ytsInit() {
  30. $yts.event(document, "spfdone", function () {
  31. ytsInitPlayer();
  32. });
  33. ytsReopenMenu();
  34. ytsBuildApp();
  35. }
  36.  
  37. function ytsBuildApp() {
  38.  
  39. yts_el_menu = $yts.get('.ytp-panel-menu');
  40. if (yts_el_menu !== null) {
  41. setTimeout(ytsRemoveDefaultSpeed, yts_remove_timeout);
  42. ytsInitSlider();
  43. ytsInitMenu();
  44. ytsInitPlayer();
  45. }
  46. else {
  47. setTimeout(ytsBuildApp, yts_build_timeout);
  48. }
  49. }
  50.  
  51.  
  52. /*************************************
  53. * MENU *
  54. ************************************/
  55.  
  56. function ytsInitMenu() {
  57.  
  58. var speedMenu = $yts.new('div', {'className': 'ytp-menuitem', id: 'yts-menu'});
  59. var right = $yts.new('div', {'className': 'ytp-menuitem-content'});
  60. right.appendChild(yts_el_slider_check);
  61. right.appendChild(yts_el_slider);
  62. speedMenu.appendChild(yts_el_slider_label);
  63. speedMenu.appendChild(right);
  64. yts_el_menu.appendChild(speedMenu);
  65. }
  66.  
  67. function ytsRemoveDefaultSpeed() {
  68. var switchers = $yts.getOpt(".ytp-menuitem", {role: 'menuitemcheckbox'});
  69. var toRemove = null;
  70.  
  71. if (!ytsPlayerHasClass('ad-interrupting') && switchers && switchers.length && !yts_event_em_speed) {
  72. toRemove = switchers[switchers.length - 1].nextElementSibling;
  73. if (toRemove && toRemove.id !== 'yts-menu') {
  74. $yts.style(toRemove, 'display', 'none');
  75. yts_event_em_speed = true;
  76. }
  77. }
  78. }
  79.  
  80. function ytsReopenMenu() {
  81. var settings_button = $yts.get(".ytp-settings-button");
  82. settings_button && settings_button.click();
  83. settings_button && settings_button.click();
  84. }
  85.  
  86.  
  87. /*************************************
  88. * SLIDER *
  89. ************************************/
  90.  
  91. function ytsInitSlider() {
  92. var rem = ytsParam(yts_r);
  93. var speed = ytsParam(yts_s) || 1;
  94. speed = rem ? speed : 1;
  95.  
  96. yts_el_slider_label = $yts.new('div', {'className': 'ytp-menuitem-label'});
  97. yts_el_slider_check = $yts.new('input', {
  98. 'type': 'checkbox',
  99. 'title': 'Remember speed',
  100. style: {
  101. 'width': '20px',
  102. 'height': '20px',
  103. 'margin': '0',
  104. 'padding': '0'
  105. }
  106. });
  107. yts_el_slider = $yts.new('input', {
  108. 'type': 'range',
  109. 'min': 0.5,
  110. 'max': 4,
  111. 'step': 0.1,
  112. 'value': speed,
  113. style: {
  114. 'width': 'calc(100% - 30px)',
  115. 'margin': '0 5px',
  116. 'padding': '0'
  117. }
  118. });
  119. if(rem){
  120. yts_el_slider_check.checked = true;
  121. }
  122. $yts.event(yts_el_slider, 'change', ytsChangeSlider);
  123. $yts.event(yts_el_slider_check, 'change', ytsChangeRemember);
  124. $yts.event(yts_el_slider, 'input', ytsChangeSlider);
  125. $yts.event(yts_el_slider, 'wheel', ytsWheelSlider);
  126.  
  127. ytsUpdateSliderLabel(speed);
  128. }
  129.  
  130.  
  131. function ytsWheelSlider(event) {
  132. var val = parseFloat(event.target.value) + (event.wheelDelta > 0 ? 0.1 : -0.1);
  133. val = val < 0.5 ? 0.5 : (val > 4 ? 4 : val);
  134. if (event.target.value !== val) {
  135. event.target.value = val;
  136. ytsUpdateSliderLabel(val);
  137. }
  138. event.preventDefault();
  139. }
  140.  
  141. function ytsChangeSlider(event) {
  142. ytsUpdateSliderLabel(event.target.value);
  143. }
  144.  
  145. function ytsChangeRemember() {
  146. ytsParam(yts_r, ytsParam(yts_r) ? 0 : 1);
  147. }
  148.  
  149.  
  150. function ytsUpdateSliderLabel(val) {
  151. ytsSetPlayerDuration(val);
  152. yts_el_slider_label.innerHTML = 'Speed: ' + parseFloat(val).toFixed(1);
  153. }
  154.  
  155.  
  156. /*************************************
  157. * PLAYER *
  158. ************************************/
  159.  
  160. function ytsInitPlayer() {
  161. yts_el_player = $yts.get('.html5-main-video');
  162. ytsObservePlayer();
  163. if (ytsParam(yts_s) && ytsParam(yts_r)) {
  164. ytsSetPlayerDuration(ytsParam(yts_s));
  165. ytsUpdateSliderLabel(ytsParam(yts_s));
  166. }
  167.  
  168. }
  169.  
  170. function ytsPlayerHasClass (cls) {
  171. ytsInitPlayer();
  172. return yts_el_player && yts_el_player.classList.contains(cls)
  173. };
  174.  
  175. function ytsSetPlayerDuration(value) {
  176. ytsParam(yts_s, value);
  177. if (yts_el_player) {
  178. yts_el_player.playbackRate = value;
  179. }
  180. }
  181.  
  182. function ytsObservePlayer() {
  183. if (!yts_event_player) {
  184. ytsObserver(yts_el_player.parentNode.parentNode, function (mutation) {
  185. if (/html5-video-player/.test(mutation.target.className) && !yts_event_em_speed) {
  186. ytsRemoveDefaultSpeed();
  187. }
  188. });
  189. yts_event_player = true;
  190. }
  191. }
  192.  
  193.  
  194. /************************************
  195. * DOM *
  196. ************************************/
  197. $yts = {
  198. 'event': function (obj, event, callback) {
  199. obj.addEventListener(event, callback);
  200. },
  201. 'new': function (tag, option) {
  202. var element = document.createElement(tag);
  203. for (var param in option) {
  204. if (param === 'data' || param === 'style' || param === 'attr') {
  205. for (var data in option[param]) {
  206. $yts[param](element, data, option[param][data]);
  207. }
  208. }
  209. else {
  210. element[param] = option[param];
  211. }
  212. }
  213. return element;
  214. },
  215. 'get': function (tselector, all) {
  216. all = all || false;
  217. var type = tselector.substring(0, 1);
  218. var selector = tselector.substring(1);
  219. var elements;
  220. if (type === "#") {
  221. return document.getElementById(selector);
  222. }
  223. else if (type === ".") {
  224. elements = document.getElementsByClassName(selector);
  225. }
  226. else {
  227. elements = document.querySelectorAll(tselector);
  228. }
  229.  
  230. if (all) {
  231. return elements;
  232. }
  233. else {
  234. return elements.length ? elements[0] : null;
  235. }
  236. },
  237. 'data': function (elem, key, val) {
  238. key = key.replace(/-(\w)/gi, function (x) {
  239. return x.charAt(1).toUpperCase()
  240. });
  241. if (typeof val !== 'undefined') {
  242. elem.dataset[key] = val;
  243. }
  244. return elem.dataset[key];
  245.  
  246. },
  247. 'style': function (elem, key, val, priority) {
  248. priority = priority || '';
  249. if (typeof val !== 'undefined') {
  250. elem.style.setProperty(key, val, priority);
  251. }
  252. return elem.style.getPropertyValue(key);
  253.  
  254. },
  255. 'attr': function (elem, key, val) {
  256. if (typeof val !== 'undefined') {
  257. elem.setAttribute(key, val);
  258. }
  259. return elem.getAttribute(key);
  260.  
  261. },
  262. 'getOpt': function (selector, option) {
  263. var el = $yts.get(selector, true);
  264. var pass = [];
  265. var correct;
  266. for (var i = 0; i < el.length; i++) {
  267. correct = true;
  268. for (var prop in option) {
  269. if (!$yts.has(el[i], prop, option[prop])) {
  270. correct = false;
  271. break;
  272. }
  273. }
  274. if (correct) {
  275. pass.push(el[i]);
  276. }
  277. }
  278. return pass;
  279. },
  280. 'has': function (elem, key, val) {
  281. if (elem.hasAttribute(key)) {
  282. var attr = elem.getAttribute(key);
  283. if (val !== null) {
  284. return attr == val;
  285. }
  286. return true;
  287. }
  288. return false;
  289. }
  290. };
  291.  
  292. /*************************************
  293. * OBSERVER *
  294. ************************************/
  295. function ytsObserver(element, callback) {
  296. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  297. if (MutationObserver) {
  298. var obs = new MutationObserver(function (mutations) {
  299. callback(mutations[0]);
  300. });
  301.  
  302. obs.observe(element, {
  303. childList: true,
  304. subtree: true,
  305. attributes: true,
  306. characterData: true,
  307. attributeOldValue: true,
  308. characterDataOldValue: true
  309. });
  310. }
  311. }
  312.  
  313. function ytsParam(key, val) {
  314. if (typeof val !== 'undefined') {
  315. localStorage.setItem(key, val);
  316. }
  317. return parseFloat(localStorage.getItem(key));
  318. }
  319.  
  320.  
  321. ytsInit();