YouTube JS Engine Tamer

To enhance YouTube performance by modifying YouTube JS Engine

目前为 2023-08-29 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube JS Engine Tamer
  3. // @namespace UserScripts
  4. // @match https://www.youtube.com/*
  5. // @version 0.3.0
  6. // @license MIT
  7. // @author CY Fung
  8. // @icon https://github.com/cyfung1031/userscript-supports/raw/main/icons/yt-engine.png
  9. // @description To enhance YouTube performance by modifying YouTube JS Engine
  10. // @grant none
  11. // @run-at document-start
  12. // @unwrap
  13. // @inject-into page
  14. // @allFrames true
  15. // ==/UserScript==
  16.  
  17. (() => {
  18.  
  19. const NATIVE_CANVAS_ANIMATION = true; // for #cinematics
  20. const FIX_schedulerInstanceInstance_ = true;
  21. const FIX_yt_player = true;
  22. const FIX_Animation_n_timeline = true;
  23.  
  24. const Promise = (async () => { })().constructor;
  25.  
  26. let isMainWindow = false;
  27. try {
  28. isMainWindow = window.document === window.top.document
  29. } catch (e) { }
  30.  
  31. const onRegistryReady = (callback) => {
  32. if (typeof customElements === 'undefined') {
  33. if (!('__CE_registry' in document)) {
  34. // https://github.com/webcomponents/polyfills/
  35. Object.defineProperty(document, '__CE_registry', {
  36. get() {
  37. // return undefined
  38. },
  39. set(nv) {
  40. if (typeof nv == 'object') {
  41. delete this.__CE_registry;
  42. this.__CE_registry = nv;
  43. this.dispatchEvent(new CustomEvent(EVENT_KEY_ON_REGISTRY_READY));
  44. }
  45. return true;
  46. },
  47. enumerable: false,
  48. configurable: true
  49. })
  50. }
  51. let eventHandler = (evt) => {
  52. document.removeEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  53. const f = callback;
  54. callback = null;
  55. eventHandler = null;
  56. f();
  57. };
  58. document.addEventListener(EVENT_KEY_ON_REGISTRY_READY, eventHandler, false);
  59. } else {
  60. callback();
  61. }
  62. };
  63.  
  64. const getZq = (_yt_player) => {
  65.  
  66.  
  67. for (const [k, v] of Object.entries(_yt_player)) {
  68.  
  69. const p = typeof v === 'function' ? v.prototype : 0;
  70. if (p
  71. && typeof p.start === 'function'
  72. && typeof p.isActive === 'function'
  73. && typeof p.stop === 'function') {
  74.  
  75. return k;
  76.  
  77. }
  78.  
  79. }
  80.  
  81.  
  82. }
  83.  
  84.  
  85. const getVG = (_yt_player) => {
  86.  
  87.  
  88. for (const [k, v] of Object.entries(_yt_player)) {
  89.  
  90. const p = typeof v === 'function' ? v.prototype : 0;
  91. if (p
  92. && typeof p.show === 'function' && p.show.length === 1
  93. && typeof p.hide === 'function' && p.hide.length === 0
  94. && typeof p.stop === 'function' && p.stop.length === 0) {
  95.  
  96. return k;
  97.  
  98. }
  99.  
  100. }
  101.  
  102.  
  103. }
  104.  
  105. let foregroundPromise = null;
  106.  
  107. const getForegroundPromise = () => {
  108. if (document.visibilityState === 'visible') return Promise.resolve();
  109. else {
  110. return foregroundPromise = foregroundPromise || new Promise(resolve => {
  111. requestAnimationFrame(() => {
  112. foregroundPromise = null;
  113. resolve();
  114. });
  115. });
  116. }
  117. }
  118.  
  119. // << if FIX_schedulerInstanceInstance_ >>
  120.  
  121. let idleFrom = Date.now() + 2700;
  122. let slowMode = false;
  123.  
  124. let ytEvented = false;
  125.  
  126.  
  127. function setupEvents() {
  128.  
  129. document.addEventListener('yt-navigate', () => {
  130.  
  131. ytEvented = true;
  132. slowMode = false;
  133. idleFrom = Date.now() + 2700;
  134.  
  135. });
  136. document.addEventListener('yt-navigate-start', () => {
  137.  
  138. ytEvented = true;
  139. slowMode = false;
  140. idleFrom = Date.now() + 2700;
  141.  
  142. });
  143.  
  144. document.addEventListener('yt-page-type-changed', () => {
  145.  
  146. ytEvented = true;
  147. slowMode = false;
  148. idleFrom = Date.now() + 1700;
  149.  
  150. });
  151.  
  152.  
  153. document.addEventListener('yt-player-updated', () => {
  154.  
  155. ytEvented = true;
  156. slowMode = false;
  157. idleFrom = Date.now() + 1700;
  158.  
  159. });
  160.  
  161.  
  162. document.addEventListener('yt-page-data-fetched', () => {
  163.  
  164. ytEvented = true;
  165. slowMode = false;
  166. idleFrom = Date.now() + 1700;
  167.  
  168. });
  169.  
  170. document.addEventListener('yt-navigate-finish', () => {
  171.  
  172. ytEvented = true;
  173. slowMode = false;
  174. let t = Date.now() + 700;
  175. if (t > idleFrom) idleFrom = t;
  176.  
  177. });
  178.  
  179. document.addEventListener('yt-page-data-updated', () => {
  180.  
  181. ytEvented = true;
  182. slowMode = false;
  183. let t = Date.now() + 700;
  184. if (t > idleFrom) idleFrom = t;
  185.  
  186. });
  187.  
  188. document.addEventListener('yt-watch-comments-ready', () => {
  189.  
  190. ytEvented = true;
  191. slowMode = false;
  192. let t = Date.now() + 700;
  193. if (t > idleFrom) idleFrom = t;
  194.  
  195. });
  196. }
  197.  
  198.  
  199. // << end >>
  200.  
  201. const cleanContext = async (win) => {
  202. const waitFn = requestAnimationFrame; // shall have been binded to window
  203. try {
  204. let mx = 16; // MAX TRIAL
  205. const frameId = 'vanillajs-iframe-v1';
  206. /** @type {HTMLIFrameElement | null} */
  207. let frame = document.getElementById(frameId);
  208. let removeIframeFn = null;
  209. if (!frame) {
  210. frame = document.createElement('iframe');
  211. frame.id = 'vanillajs-iframe-v1';
  212. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  213. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  214. n.appendChild(frame);
  215. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  216. const root = document.documentElement;
  217. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  218. removeIframeFn = (setTimeout) => {
  219. const removeIframeOnDocumentReady = (e) => {
  220. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  221. win = null;
  222. setTimeout(() => {
  223. n.remove();
  224. n = null;
  225. }, 200);
  226. }
  227. if (document.readyState !== 'loading') {
  228. removeIframeOnDocumentReady();
  229. } else {
  230. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  231. }
  232. }
  233. }
  234. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  235. const fc = frame.contentWindow;
  236. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  237. const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle } = fc;
  238. const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle };
  239. for (let k in res) res[k] = res[k].bind(win); // necessary
  240. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  241. res.animate = fc.HTMLElement.prototype.animate;
  242. return res;
  243. } catch (e) {
  244. console.warn(e);
  245. return null;
  246. }
  247. };
  248.  
  249.  
  250. const promiseForCustomYtElementsReady = new Promise(onRegistryReady);
  251.  
  252. cleanContext(window).then(__CONTEXT__ => {
  253. if (!__CONTEXT__) return null;
  254.  
  255. const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, animate, requestIdleCallback, getComputedStyle } = __CONTEXT__;
  256.  
  257.  
  258.  
  259. let rafPromiseForTickers = null;
  260. const getRafPromiseForTickers = () => (rafPromiseForTickers = (rafPromiseForTickers || new Promise(resolve => {
  261. requestAnimationFrame((hRes) => {
  262. rafPromiseForTickers = null;
  263. resolve(hRes);
  264. });
  265. })));
  266.  
  267. const promiseForTamerTimeout = new Promise(resolve => {
  268. promiseForCustomYtElementsReady.then(() => {
  269. customElements.whenDefined('ytd-app').then(() => {
  270. setTimeout(resolve, 1200);
  271. });
  272. });
  273. setTimeout(resolve, 3000);
  274. });
  275.  
  276.  
  277. class RAFHub {
  278. constructor() {
  279. /** @type {number} */
  280. this.startAt = 8170;
  281. /** @type {number} */
  282. this.counter = 0;
  283. /** @type {number} */
  284. this.rid = 0;
  285. /** @type {Map<number, FrameRequestCallback>} */
  286. this.funcs = new Map();
  287. const funcs = this.funcs;
  288. /** @type {FrameRequestCallback} */
  289. this.bCallback = this.mCallback.bind(this);
  290. this.pClear = () => funcs.clear();
  291. }
  292. /** @param {DOMHighResTimeStamp} highResTime */
  293. mCallback(highResTime) {
  294. this.rid = 0;
  295. Promise.resolve().then(this.pClear);
  296. this.funcs.forEach(func => Promise.resolve(highResTime).then(func).catch(console.warn));
  297. }
  298. /** @param {FrameRequestCallback} f */
  299. request(f) {
  300. if (this.counter > 1e9) this.counter = 9;
  301. let cid = this.startAt + (++this.counter);
  302. this.funcs.set(cid, f);
  303. if (this.rid === 0) this.rid = requestAnimationFrame(this.bCallback);
  304. return cid;
  305. }
  306. /** @param {number} cid */
  307. cancel(cid) {
  308. cid = +cid;
  309. if (cid > 0) {
  310. if (cid <= this.startAt) {
  311. return cancelAnimationFrame(cid);
  312. }
  313. if (this.rid > 0) {
  314. this.funcs.delete(cid);
  315. if (this.funcs.size === 0) {
  316. cancelAnimationFrame(this.rid);
  317. this.rid = 0;
  318. }
  319. }
  320. }
  321. }
  322. }
  323.  
  324.  
  325.  
  326. NATIVE_CANVAS_ANIMATION && (() => {
  327.  
  328. HTMLCanvasElement.prototype.animate = animate;
  329.  
  330. let cid = setInterval(() => {
  331. HTMLCanvasElement.prototype.animate = animate;
  332. }, 1);
  333.  
  334. promiseForTamerTimeout.then(() => {
  335. clearInterval(cid)
  336. });
  337.  
  338. })();
  339.  
  340.  
  341. FIX_schedulerInstanceInstance_ && (async () => {
  342.  
  343.  
  344. const schedulerInstanceInstance_ = await new Promise(resolve => {
  345.  
  346. let cid = setInterval(() => {
  347. let t = (((window || 0).ytglobal || 0).schedulerInstanceInstance_ || 0);
  348. if (t) {
  349.  
  350. clearInterval(cid);
  351. resolve(t);
  352. }
  353. }, 1);
  354. promiseForTamerTimeout.then(() => {
  355. resolve(null)
  356. });
  357. });
  358.  
  359. if (!schedulerInstanceInstance_) return;
  360.  
  361.  
  362. if (!ytEvented) {
  363. idleFrom = Date.now() + 2700;
  364. slowMode = false; // integrity
  365. }
  366.  
  367. const checkOK = typeof schedulerInstanceInstance_.start === 'function' && !schedulerInstanceInstance_.start991 && !schedulerInstanceInstance_.stop && !schedulerInstanceInstance_.cancel && !schedulerInstanceInstance_.terminate && !schedulerInstanceInstance_.interupt;
  368. if (checkOK) {
  369.  
  370. schedulerInstanceInstance_.start991 = schedulerInstanceInstance_.start;
  371.  
  372. let requestingFn = null;
  373. let requestingArgs = null;
  374. let requestingDT = 0;
  375.  
  376. let timerId = null;
  377. const entries = [];
  378. const f = function () {
  379. requestingFn = this.fn;
  380. requestingArgs = [...arguments];
  381. requestingDT = Date.now();
  382. entries.push({
  383. fn: requestingFn,
  384. args: requestingArgs,
  385. t: requestingDT
  386. });
  387. // if (Date.now() < idleFrom) {
  388. // timerId = this.fn.apply(window, arguments);
  389. // } else {
  390. // timerId = this.fn.apply(window, arguments);
  391.  
  392. // }
  393. // timerId = 12377;
  394. return 12377;
  395. }
  396.  
  397.  
  398. const fakeFns = [
  399. f.bind({ fn: requestAnimationFrame }),
  400. f.bind({ fn: setInterval }),
  401. f.bind({ fn: setTimeout }),
  402. f.bind({ fn: requestIdleCallback })
  403. ]
  404.  
  405.  
  406.  
  407.  
  408. let timerResolve = null;
  409. setInterval(() => {
  410. timerResolve && timerResolve();
  411. timerResolve = null;
  412. if (!slowMode && Date.now() > idleFrom) slowMode = true;
  413. }, 250);
  414.  
  415. let mzt = 0;
  416.  
  417. let fnSelectorProp = null;
  418.  
  419. schedulerInstanceInstance_.start = function () {
  420.  
  421. const mk1 = window.requestAnimationFrame
  422. const mk2 = window.setInterval
  423. const mk3 = window.setTimeout
  424. const mk4 = window.requestIdleCallback
  425.  
  426. const tThis = this['$$12378$$'] || this;
  427.  
  428.  
  429. window.requestAnimationFrame = fakeFns[0]
  430. window.setInterval = fakeFns[1]
  431. window.setTimeout = fakeFns[2]
  432. window.requestIdleCallback = fakeFns[3]
  433.  
  434. fnSelectorProp = null;
  435.  
  436.  
  437. tThis.start991.call(new Proxy(tThis, {
  438. get(target, prop, receiver) {
  439. if (prop === '$$12377$$') return true;
  440. if (prop === '$$12378$$') return target;
  441.  
  442. // console.log('get',prop)
  443. return target[prop]
  444. },
  445. set(target, prop, value, receiver) {
  446. // console.log('set', prop, value)
  447.  
  448.  
  449. if (value >= 1 && value <= 4) fnSelectorProp = prop;
  450. if (value === 12377 && fnSelectorProp) {
  451.  
  452. const originalSelection = target[fnSelectorProp];
  453. const timerIdProp = prop;
  454.  
  455. /*
  456.  
  457.  
  458. case 1:
  459. var a = this.K;
  460. this.g = this.I ? window.requestIdleCallback(a, {
  461. timeout: 3E3
  462. }) : window.setTimeout(a, ma);
  463. break;
  464. case 2:
  465. this.g = window.setTimeout(this.M, this.N);
  466. break;
  467. case 3:
  468. this.g = window.requestAnimationFrame(this.L);
  469. break;
  470. case 4:
  471. this.g = window.setTimeout(this.J, 0)
  472. }
  473.  
  474. */
  475.  
  476. const doForegroundSlowMode = () => {
  477.  
  478. const tir = ++mzt;
  479. const f = requestingArgs[0];
  480.  
  481.  
  482. getForegroundPromise().then(() => {
  483.  
  484.  
  485. new Promise(r => {
  486. timerResolve = r
  487. }).then(() => {
  488. if (target[timerIdProp] === -tir) f();
  489. });
  490.  
  491. })
  492.  
  493. target[fnSelectorProp] = 931;
  494. target[prop] = -tir;
  495. }
  496.  
  497. if (target[fnSelectorProp] === 2 && requestingFn === setTimeout) {
  498. if (slowMode && !(requestingArgs[1] > 250)) {
  499.  
  500. doForegroundSlowMode();
  501.  
  502. } else {
  503. target[prop] = setTimeout.apply(window, requestingArgs);
  504.  
  505. }
  506.  
  507. } else if (target[fnSelectorProp] === 3 && requestingFn === requestAnimationFrame) {
  508.  
  509. if (slowMode) {
  510.  
  511. doForegroundSlowMode();
  512.  
  513. } else {
  514. target[prop] = requestAnimationFrame.apply(window, requestingArgs);
  515. }
  516.  
  517.  
  518. } else if (target[fnSelectorProp] === 4 && requestingFn === setTimeout && !requestingArgs[1]) {
  519.  
  520. const f = requestingArgs[0];
  521. const tir = ++mzt;
  522. Promise.resolve().then(() => {
  523. if (target[timerIdProp] === -tir) f();
  524. });
  525. target[fnSelectorProp] = 930;
  526. target[prop] = -tir;
  527.  
  528. } else if (target[fnSelectorProp] === 1 && (requestingFn === requestIdleCallback || requestingFn === setTimeout)) {
  529.  
  530. doForegroundSlowMode();
  531.  
  532. } else {
  533. // target[prop] = timerId;
  534. target[fnSelectorProp] = 0;
  535. target[prop] = 0;
  536. }
  537.  
  538. // *****
  539. // console.log('[[set]]', slowMode , prop, value, `fnSelectorProp: ${originalSelection} -> ${target[fnSelectorProp]}`)
  540. } else {
  541.  
  542. target[prop] = value;
  543. }
  544. // console.log('set',prop,value)
  545. return true;
  546. }
  547. }));
  548.  
  549. fnSelectorProp = null;
  550.  
  551.  
  552. window.requestAnimationFrame = mk1;
  553. window.setInterval = mk2
  554. window.setTimeout = mk3
  555. window.requestIdleCallback = mk4;
  556.  
  557.  
  558.  
  559. }
  560.  
  561. schedulerInstanceInstance_.start.toString = function () {
  562. return schedulerInstanceInstance_.start991.toString();
  563. }
  564.  
  565. // const funcNames = [...(schedulerInstanceInstance_.start + "").matchAll(/[\(,]this\.(\w{1,2})[,\)]/g)].map(e => e[1]).map(prop => ({
  566. // prop,
  567. // value: schedulerInstanceInstance_[prop],
  568. // type: typeof schedulerInstanceInstance_[prop]
  569.  
  570. // }));
  571. // console.log('fcc', funcNames)
  572.  
  573.  
  574.  
  575.  
  576. }
  577. })();
  578.  
  579.  
  580. FIX_yt_player && (async () => {
  581.  
  582.  
  583.  
  584. const rafHub = new RAFHub();
  585.  
  586.  
  587. const _yt_player = await new Promise(resolve => {
  588.  
  589. let cid = setInterval(() => {
  590. let t = (((window || 0)._yt_player || 0) || 0);
  591. if (t) {
  592.  
  593. clearInterval(cid);
  594. resolve(t);
  595. }
  596. }, 1);
  597.  
  598. promiseForTamerTimeout.then(() => {
  599. resolve(null)
  600. });
  601.  
  602. });
  603.  
  604.  
  605.  
  606. if (!_yt_player || typeof _yt_player !== 'object') return;
  607.  
  608.  
  609.  
  610. let keyZq = getZq(_yt_player);
  611. let keyVG = getVG(_yt_player);
  612. let buildVG = _yt_player[keyVG];
  613. let u = new buildVG({
  614. api: {},
  615. element: document.createElement('noscript'),
  616. api: {},
  617. hide: () => { }
  618. }, 250);
  619. // console.log(keyVG, u)
  620. // buildVG.prototype.show = function(){}
  621. // _yt_player[keyZq] = g.k
  622.  
  623. if (!keyZq) return;
  624.  
  625.  
  626. const g = _yt_player
  627. let k = keyZq
  628.  
  629. const gk = g[k];
  630. if (typeof gk !== 'function') return;
  631.  
  632. let dummyObject = new gk;
  633. let nilFunc = () => { };
  634.  
  635. let nilObj = {};
  636.  
  637. // console.log(1111111111)
  638.  
  639. let keyBoolD = '';
  640. let keyWindow = '';
  641. let keyFuncC = '';
  642. let keyCidj = '';
  643.  
  644. for (const [t, y] of Object.entries(dummyObject)) {
  645. if (y instanceof Window) keyWindow = t;
  646. }
  647.  
  648. const dummyObjectProxyHandler = {
  649. get(target, prop) {
  650. let v = target[prop]
  651. if (v instanceof Window && !keyWindow) {
  652. keyWindow = t;
  653. }
  654. let y = typeof v === 'function' ? nilFunc : typeof v === 'object' ? nilObj : v;
  655. if (prop === keyWindow) y = {
  656. requestAnimationFrame(f) {
  657. return 3;
  658. },
  659. cancelAnimationFrame() {
  660.  
  661. }
  662. }
  663. if (!keyFuncC && typeof v === 'function' && !(prop in target.constructor.prototype)) {
  664. keyFuncC = prop;
  665. }
  666. // console.log('[get]', prop, typeof target[prop])
  667.  
  668.  
  669. return y;
  670. },
  671. set(target, prop, value) {
  672.  
  673. if (typeof value === 'boolean' && !keyBoolD) {
  674. keyBoolD = prop;
  675. }
  676. if (typeof value === 'number' && !keyCidj && value >= 2) {
  677. keyCidj = prop;
  678. }
  679.  
  680. // console.log('[set]', prop, value)
  681. target[prop] = value
  682.  
  683. return true;
  684. }
  685. };
  686.  
  687. dummyObject.start.call(new Proxy(dummyObject, dummyObjectProxyHandler))
  688.  
  689. /*
  690. console.log({
  691. keyBoolD,
  692. keyFuncC,
  693. keyWindow,
  694. keyCidj
  695. })
  696.  
  697. console.log( dummyObject[keyFuncC])
  698.  
  699.  
  700. console.log(2222222222)
  701. */
  702.  
  703.  
  704.  
  705.  
  706. g[k].prototype.start = function () {
  707. this.stop();
  708. this[keyBoolD] = true;
  709. this[keyCidj] = rafHub.request(this[keyFuncC]);
  710. }
  711. ;
  712. g[k].prototype.stop = function () {
  713. if (this.isActive() && this[keyCidj]) {
  714. rafHub.cancel(this[keyCidj]);
  715. }
  716. this[keyCidj] = null
  717. }
  718.  
  719.  
  720. /*
  721. g[k].start = function() {
  722. this.stop();
  723. this.D = true;
  724. var a = requestAnimationFrame
  725. , b = cancelAnimationFrame;
  726. this.j = a.call(this.B, this.C)
  727. }
  728. ;
  729. g[k].stop = function() {
  730. if (this.isActive()) {
  731. var a = requestAnimationFrame
  732. , b = cancelAnimationFrame;
  733. b.call(this.B, this.j)
  734. }
  735. this.j = null
  736. }
  737. */
  738.  
  739.  
  740.  
  741. })();
  742.  
  743.  
  744.  
  745. FIX_Animation_n_timeline && (async () => {
  746.  
  747.  
  748. const timeline = await new Promise(resolve => {
  749.  
  750. let cid = setInterval(() => {
  751. let t = (((document || 0).timeline || 0) || 0);
  752. if (t && typeof t._play === 'function') {
  753.  
  754. clearInterval(cid);
  755. resolve(t);
  756. }
  757. }, 1);
  758.  
  759. promiseForTamerTimeout.then(() => {
  760. resolve(null)
  761. });
  762.  
  763. });
  764.  
  765.  
  766. const Animation = await new Promise(resolve => {
  767.  
  768. let cid = setInterval(() => {
  769. let t = (((window || 0).Animation || 0) || 0);
  770. if (t && typeof t === 'function' && t.length === 2 && typeof t.prototype._updatePromises === 'function') {
  771.  
  772. clearInterval(cid);
  773. resolve(t);
  774. }
  775. }, 1);
  776.  
  777. promiseForTamerTimeout.then(() => {
  778. resolve(null)
  779. });
  780.  
  781. });
  782.  
  783. if (!timeline) return;
  784. if (!Animation) return;
  785.  
  786. const aniProto = Animation.prototype;
  787.  
  788. const getXroto = (x) => {
  789. try {
  790. return x.__proto__;
  791. } catch (e) { }
  792. return null;
  793. }
  794. const timProto = getXroto(timeline);
  795. if (!timProto) return;
  796. if (
  797. (
  798. typeof timProto.getAnimations === 'function' && typeof timProto.play === 'function' &&
  799. typeof timProto._discardAnimations === 'function' && typeof timProto._play === 'function' &&
  800. typeof timProto._updateAnimationsPromises === 'function' && !timProto.nofCQ &&
  801. typeof aniProto._updatePromises === 'function' && !aniProto.nofYH
  802. )
  803.  
  804. ) {
  805.  
  806. timProto.nofCQ = 1;
  807. aniProto.nofYH = 1;
  808.  
  809. const originalAnimationsWithPromises = ((_updateAnimationsPromises) => {
  810.  
  811.  
  812. /*
  813. v.animationsWithPromises = v.animationsWithPromises.filter(function (c) {
  814. return c._updatePromises();
  815. });
  816. */
  817.  
  818. const p = Array.prototype.filter;
  819.  
  820. let res = null;
  821. Array.prototype.filter = function () {
  822.  
  823. res = this;
  824. return this;
  825.  
  826. };
  827.  
  828. _updateAnimationsPromises.call({});
  829.  
  830. Array.prototype.filter = p;
  831.  
  832. if (res && typeof res.length === 'number') {
  833. /** @type {any[]} */
  834. const _res = res;
  835. return _res;
  836. }
  837.  
  838.  
  839. return null;
  840.  
  841.  
  842.  
  843.  
  844. })(timProto._updateAnimationsPromises);
  845.  
  846. if (!originalAnimationsWithPromises || typeof originalAnimationsWithPromises.length !== 'number') return;
  847.  
  848. // console.log('originalAnimationsWithPromises', originalAnimationsWithPromises)
  849.  
  850. aniProto._updatePromises31 = aniProto._updatePromises;
  851.  
  852. /*
  853. aniProto._updatePromises = function(){
  854. console.log('eff',this._oldPlayState, this.playState)
  855. return this._updatePromises31.apply(this, arguments)
  856. }
  857. */
  858.  
  859. aniProto._updatePromises = function () {
  860. var oldPlayState = this._oldPlayState;
  861. var newPlayState = this.playState;
  862. // console.log('ett', oldPlayState, newPlayState)
  863. if (newPlayState !== oldPlayState) {
  864. this._oldPlayState = newPlayState;
  865. if (this._readyPromise) {
  866. if ("idle" == newPlayState) {
  867. this._rejectReadyPromise();
  868. this._readyPromise = void 0;
  869. } else if ("pending" == oldPlayState) {
  870. this._resolveReadyPromise();
  871. } else if ("pending" == newPlayState) {
  872. this._readyPromise = void 0;
  873. }
  874. }
  875. if (this._finishedPromise) {
  876. if ("idle" == newPlayState) {
  877. this._rejectFinishedPromise();
  878. this._finishedPromise = void 0;
  879. } else if ("finished" == newPlayState) {
  880. this._resolveFinishedPromise();
  881. } else if ("finished" == oldPlayState) {
  882. this._finishedPromise = void 0;
  883. }
  884. }
  885. }
  886. return this._readyPromise || this._finishedPromise;
  887. };
  888.  
  889.  
  890. let restartWebAnimationsNextTickFlag = false;
  891.  
  892. const looperMethodT = () => {
  893.  
  894. const runnerFn = (hRes) => {
  895. var b = timeline;
  896. b.currentTime = hRes;
  897. b._discardAnimations();
  898. if (0 == b._animations.length) {
  899. restartWebAnimationsNextTickFlag = false;
  900. } else {
  901. getForegroundPromise().then(runnerFn);
  902. }
  903. }
  904.  
  905. const restartWebAnimationsNextTick = () => {
  906. if (!restartWebAnimationsNextTickFlag) {
  907. restartWebAnimationsNextTickFlag = true;
  908. getRafPromiseForTickers().then(runnerFn);
  909. }
  910. }
  911.  
  912. return { restartWebAnimationsNextTick }
  913. };
  914.  
  915.  
  916. const looperMethodN = () => {
  917.  
  918. const acs = document.createElement('a-f');
  919. acs.id = 'a-f';
  920.  
  921. const style = document.createElement('style');
  922. style.textContent = `
  923. @keyFrames aF1 {
  924. 0% {
  925. order: 0;
  926. }
  927. 100% {
  928. order: 6;
  929. }
  930. }
  931. #a-f[id] {
  932. visibility: collapse !important;
  933. position: fixed !important;
  934. top: -100px !important;
  935. left: -100px !important;
  936. margin:0 !important;
  937. padding:0 !important;
  938. outline:0 !important;
  939. border:0 !important;
  940. z-index:-1 !important;
  941. width: 0px !important;
  942. height: 0px !important;
  943. contain: strict !important;
  944. pointer-events: none !important;
  945. animation: 1ms steps(2) 0ms infinite alternate forwards running aF1 !important;
  946. }
  947. `;
  948. (document.head || document.documentElement).appendChild(style);
  949.  
  950. document.documentElement.insertBefore(acs, document.documentElement.firstChild);
  951.  
  952. const _onanimationiteration = function (evt) {
  953. const hRes = evt.timeStamp;
  954. var b = timeline;
  955. b.currentTime = hRes;
  956. b._discardAnimations();
  957. if (0 == b._animations.length) {
  958. restartWebAnimationsNextTickFlag = false;
  959. acs.onanimationiteration = null;
  960. } else {
  961. acs.onanimationiteration = _onanimationiteration;
  962. }
  963.  
  964. }
  965.  
  966.  
  967.  
  968. const restartWebAnimationsNextTick = () => {
  969. if (!restartWebAnimationsNextTickFlag) {
  970. restartWebAnimationsNextTickFlag = true;
  971. acs.onanimationiteration = _onanimationiteration;
  972.  
  973. }
  974. }
  975.  
  976. return { restartWebAnimationsNextTick }
  977. };
  978.  
  979.  
  980.  
  981. const { restartWebAnimationsNextTick } = ('onanimationiteration' in document.documentElement) ? looperMethodN() : looperMethodT();
  982.  
  983. timProto._play = function (c) {
  984. c = new Animation(c, this);
  985. this._animations.push(c);
  986. restartWebAnimationsNextTick();
  987. c._updatePromises();
  988. c._animation.play();
  989. c._updatePromises();
  990. return c
  991. }
  992.  
  993. const animationsWithPromisesMap = new Set(originalAnimationsWithPromises);
  994. originalAnimationsWithPromises.length = 0;
  995. originalAnimationsWithPromises.push = null;
  996. originalAnimationsWithPromises.splice = null;
  997. originalAnimationsWithPromises.slice = null;
  998. originalAnimationsWithPromises.indexOf = null;
  999. originalAnimationsWithPromises.unshift = null;
  1000. originalAnimationsWithPromises.shift = null;
  1001. originalAnimationsWithPromises.pop = null;
  1002. originalAnimationsWithPromises.filter = null;
  1003. originalAnimationsWithPromises.forEach = null;
  1004. originalAnimationsWithPromises.map = null;
  1005.  
  1006.  
  1007.  
  1008. const _updateAnimationsPromises = () => {
  1009. animationsWithPromisesMap.forEach(c => {
  1010. if (!c._updatePromises()) animationsWithPromisesMap.delete(c);
  1011. })
  1012. /*
  1013. v.animationsWithPromises = v.animationsWithPromises.filter(function (c) {
  1014. return c._updatePromises();
  1015. });
  1016. */
  1017. }
  1018.  
  1019. timProto._updateAnimationsPromises31 = timProto._updateAnimationsPromises;
  1020.  
  1021. timProto._updateAnimationsPromises = _updateAnimationsPromises;
  1022.  
  1023.  
  1024. let pdFinished = Object.getOwnPropertyDescriptor(aniProto, 'finished');
  1025. aniProto.__finished_native_get__ = pdFinished.get;
  1026. if (typeof pdFinished.get === 'function' && !pdFinished.set && pdFinished.configurable === true && pdFinished.enumerable === true) {
  1027.  
  1028.  
  1029. Object.defineProperty(aniProto, 'finished', {
  1030. get() {
  1031. this._finishedPromise || (!animationsWithPromisesMap.has(this) && animationsWithPromisesMap.add(this),
  1032. this._finishedPromise = new Promise((resolve, reject) => {
  1033. this._resolveFinishedPromise = function () {
  1034. resolve(this)
  1035. };
  1036. this._rejectFinishedPromise = function () {
  1037. reject({
  1038. type: DOMException.ABORT_ERR,
  1039. name: "AbortError"
  1040. })
  1041. };
  1042. }),
  1043. "finished" == this.playState && this._resolveFinishedPromise());
  1044. return this._finishedPromise
  1045. },
  1046. set: undefined,
  1047. enumerable: true,
  1048. configurable: true
  1049. });
  1050.  
  1051. }
  1052.  
  1053.  
  1054.  
  1055. let pdReady = Object.getOwnPropertyDescriptor(aniProto, 'ready');
  1056. aniProto.__ready_native_get__ = pdReady.get;
  1057. if (typeof pdReady.get === 'function' && !pdReady.set && pdReady.configurable === true && pdReady.enumerable === true) {
  1058.  
  1059. Object.defineProperty(aniProto, 'ready', {
  1060. get() {
  1061. this._readyPromise || (!animationsWithPromisesMap.has(this) && animationsWithPromisesMap.add(this),
  1062. this._readyPromise = new Promise((resolve, reject) => {
  1063. this._resolveReadyPromise = function () {
  1064. resolve(this)
  1065. };
  1066. this._rejectReadyPromise = function () {
  1067. reject({
  1068. type: DOMException.ABORT_ERR,
  1069. name: "AbortError"
  1070. })
  1071. };
  1072. }),
  1073. "pending" !== this.playState && this._resolveReadyPromise());
  1074. return this._readyPromise
  1075. },
  1076. set: undefined,
  1077. enumerable: true,
  1078. configurable: true
  1079. });
  1080.  
  1081. }
  1082.  
  1083.  
  1084.  
  1085.  
  1086. /*
  1087. function f(c) {
  1088. var b = v.timeline;
  1089. b.currentTime = c;
  1090. b._discardAnimations();
  1091. 0 == b._animations.length ? d = !1 : requestAnimationFrame(f)
  1092. }
  1093. var h = window.requestAnimationFrame;
  1094. window.requestAnimationFrame = function(c) {
  1095. return h(function(b) {
  1096. v.timeline._updateAnimationsPromises();
  1097. c(b);
  1098. v.timeline._updateAnimationsPromises()
  1099. })
  1100. }
  1101. ;
  1102. v.AnimationTimeline = function() {
  1103. this._animations = [];
  1104. this.currentTime = void 0
  1105. }
  1106. ;
  1107. v.AnimationTimeline.prototype = {
  1108. getAnimations: function() {
  1109. this._discardAnimations();
  1110. return this._animations.slice()
  1111. },
  1112. _updateAnimationsPromises: function() {
  1113. v.animationsWithPromises = v.animationsWithPromises.filter(function(c) {
  1114. return c._updatePromises()
  1115. })
  1116. },
  1117. _discardAnimations: function() {
  1118. this._updateAnimationsPromises();
  1119. this._animations = this._animations.filter(function(c) {
  1120. return "finished" != c.playState && "idle" != c.playState
  1121. })
  1122. },
  1123. _play: function(c) {
  1124. c = new v.Animation(c,this);
  1125. this._animations.push(c);
  1126. v.restartWebAnimationsNextTick();
  1127. c._updatePromises();
  1128. c._animation.play();
  1129. c._updatePromises();
  1130. return c
  1131. },
  1132. play: function(c) {
  1133. c && c.remove();
  1134. return this._play(c)
  1135. }
  1136. };
  1137. var d = !1;
  1138. v.restartWebAnimationsNextTick = function() {
  1139. d || (d = !0,
  1140. requestAnimationFrame(f))
  1141. }
  1142. ;
  1143. var a = new v.AnimationTimeline;
  1144. v.timeline = a;
  1145. try {
  1146. Object.defineProperty(window.document, "timeline", {
  1147. configurable: !0,
  1148. get: function() {
  1149. return a
  1150. }
  1151. })
  1152. } catch (c) {}
  1153. try {
  1154. window.document.timeline = a
  1155. } catch (c) {}
  1156. */
  1157.  
  1158.  
  1159.  
  1160. /*
  1161.  
  1162. var g = window.getComputedStyle;
  1163. Object.defineProperty(window, "getComputedStyle", {
  1164. configurable: !0,
  1165. enumerable: !0,
  1166. value: function() {
  1167. v.timeline._updateAnimationsPromises();
  1168. var e = g.apply(this, arguments);
  1169. h() && (e = g.apply(this, arguments));
  1170. v.timeline._updateAnimationsPromises();
  1171. return e
  1172. }
  1173. });
  1174.  
  1175. */
  1176.  
  1177.  
  1178.  
  1179.  
  1180. }
  1181.  
  1182.  
  1183.  
  1184.  
  1185. })();
  1186.  
  1187.  
  1188.  
  1189. });
  1190.  
  1191.  
  1192. setupEvents();
  1193.  
  1194.  
  1195.  
  1196. if (isMainWindow) {
  1197.  
  1198. console.groupCollapsed(
  1199. "%cYouTube JS Engine Tamer",
  1200. "background-color: #EDE43B ; color: #000 ; font-weight: bold ; padding: 4px ;"
  1201. );
  1202.  
  1203. console.log("Script is loaded.");
  1204. console.log("This script changes the core mechanisms of the YouTube JS engine.");
  1205.  
  1206. console.log("This script is experimental and subject to further changes.");
  1207.  
  1208. console.log("This might boost your YouTube performance.");
  1209.  
  1210. console.log("CAUTION: This might break your YouTube.");
  1211.  
  1212. console.groupEnd();
  1213.  
  1214. }
  1215.  
  1216.  
  1217. })();