Greasy Fork is available in English.

Youtube Player perf

Optimizes animation calls for lower GPU/CPU consumption

  1.  
  2. // ==UserScript==
  3. // @name Youtube Player perf
  4. // @version 0.7
  5. // @description Optimizes animation calls for lower GPU/CPU consumption
  6. // @namespace nopeless.github.io
  7. // @author nopeless
  8. // @match https://www.youtube.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @grant none
  11. // @run-at document-start
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. /* global _yt_player */
  16.  
  17. (() => {
  18. "use strict";
  19.  
  20. const scaleX0 = Symbol("scaleX(0)");
  21. const scaleX1 = Symbol("scaleX(1)");
  22.  
  23. const PLAYER_CONSTRUCTOR = "sV";
  24. const ATTR_UPDATE = "yo";
  25. const UPDATE_INTERNAL = "v";
  26.  
  27. function scaleX(el) {
  28. el.style.transform = "scaleX(0)";
  29. el[scaleX0] = true;
  30. el[scaleX1] = false;
  31. }
  32.  
  33. function checks(ytp) {
  34. // .u
  35. // updating method
  36. return ytp[UPDATE_INTERNAL] && ytp[PLAYER_CONSTRUCTOR] && ytp[ATTR_UPDATE];
  37. }
  38.  
  39. function modifyBase() {
  40. console.log("Overriding _yt_player methods");
  41.  
  42. if (!window._yt_player) return console.error("YT player not avaliable, load order is wrong");
  43.  
  44. if (!checks(window._yt_player)) alert("YouTube Player Perf will not work in this version of youtube. Disable it. Leave a kind comment on https://greatest.deepsurf.us/en/scripts/471489-youtube-player-perf");
  45.  
  46. const PlayerConstructor = _yt_player[PLAYER_CONSTRUCTOR];
  47.  
  48. // save the original prototype
  49. const PlayerConstructorPrototype = _yt_player[PLAYER_CONSTRUCTOR].prototype;
  50.  
  51. let dirty = false;
  52.  
  53. let chapterCount = 0;
  54.  
  55. function update(a) {
  56. // 2023-08-02
  57. // .u -> .v
  58. for (var b = _yt_player[UPDATE_INTERNAL](Object.keys(a)), c = b.next(); !c.done; c = b.next()) {
  59. c = c.value;
  60.  
  61. if (this.__updateCache.get(c) !== a[c]) {
  62. // console.log("updating", c, a[c]);
  63. this.updateValue(c, a[c]);
  64. dirty = true;
  65. this.__updateCache.set(c, a[c]);
  66. }
  67. }
  68. }
  69.  
  70. // use ToolTip for checking
  71. let ytpToolTip = null;
  72. let ytpToolTipPreviousValue = null;
  73.  
  74. _yt_player[PLAYER_CONSTRUCTOR] = function (...args) {
  75. // debugger;
  76. // YouTube base.js update approx 2023-07-28
  77. // this -> args[0]
  78. // const a = args[0];
  79. // This was reverted approx 2023-08-02
  80.  
  81. PlayerConstructor.call(this, ...args);
  82.  
  83. this.__updateCache = new Map();
  84.  
  85. // console.log(this.update);
  86.  
  87. // override update
  88. // debugger;
  89. this.update = update;
  90. };
  91.  
  92. _yt_player[PLAYER_CONSTRUCTOR].prototype = Object.create(PlayerConstructorPrototype);
  93. _yt_player[PLAYER_CONSTRUCTOR].prototype.constructor = _yt_player[PLAYER_CONSTRUCTOR];
  94.  
  95. const attributeUpdate = _yt_player[ATTR_UPDATE];
  96. let headUpdateElement = null;
  97. let loopLatch = false;
  98.  
  99. // YouTube base.js update approx 2023-07-28
  100. // Nn => Un
  101.  
  102. // if ('string' === typeof b)
  103. _yt_player[ATTR_UPDATE] = (a, b, c) => {
  104. // don't do excessive progress bar updates
  105. if (b === "transform") {
  106. let ih;
  107.  
  108. if (dirty || (ih = (ytpToolTip ??= document.querySelector("span.ytp-tooltip-text"))?.innerHTML) !== ytpToolTipPreviousValue) {
  109. // first call after dirty
  110. headUpdateElement = a;
  111.  
  112. dirty = false;
  113. loopLatch = true;
  114.  
  115. ytpToolTipPreviousValue = ih;
  116. } else if (a === headUpdateElement) {
  117. headUpdateElement = null;
  118. loopLatch = false;
  119. return;
  120. } else if (!loopLatch) return;
  121.  
  122. // scalex(0) is almost useless after initial load
  123. if (c === "scalex(0)") {
  124. if (a[scaleX0]) return;
  125. a[scaleX0] = true;
  126. a[scaleX1] = false;
  127. } else if (c === "scalex(1)") {
  128. if (a[scaleX1]) return;
  129. a[scaleX0] = false;
  130. a[scaleX1] = true;
  131. } else {
  132. a[scaleX0] = false;
  133. a[scaleX1] = false;
  134. }
  135. }
  136. attributeUpdate(a, b, c);
  137. };
  138. }
  139.  
  140. window.__modifyBase = modifyBase;
  141.  
  142. const ob = new MutationObserver(mrs => {
  143. const l = mrs.map(mr => mr.addedNodes[0]).find(node => node && node.nodeName === "SCRIPT" && node.src && node.src.match(/\/base\.js$/));
  144.  
  145. if (!l) return;
  146.  
  147. l.setAttribute("onload", "__modifyBase()");
  148.  
  149. ob.disconnect();
  150. });
  151.  
  152. console.log("watching for script changes");
  153. ob.observe(document, { attributes: false, childList: true, subtree: true });
  154. })();
  155.  
  156. /* For internal use. Not relevant to script
  157.  
  158. 2023-08-05
  159.  
  160. 61267
  161. sRa = function (a) {
  162. return a.N(
  163. 'embeds_web_enable_video_data_refactoring_offline_and_progress_bar'
  164. )
  165. };
  166. g.NV = function (a, b) {
  167. g.XQ.call(
  168. this,
  169. {
  170. G: 'div',
  171. S: 'ytp-progress-bar-container',
  172. Y: {
  173. 'aria-disabled': 'true'
  174. },
  175.  
  176. 12653
  177. co = function (a, b) {
  178. return a == b ? !0 : a &&
  179. b ? a.left == b.left &&
  180. a.width == b.width &&
  181. a.top == b.top &&
  182. a.height == b.height : !1
  183. };
  184. g.ro = function (a, b, c) {
  185. if ('string' === typeof b) (b = qo(a, b)) &&
  186. (a.style[b] = c);
  187. else for (var d in b) {
  188. c = a;
  189. var e = b[d],
  190. f = qo(c, d);
  191.  
  192. g.v should be found from call stack
  193.  
  194. */