YouTube Web Tweaks

This script optimizes YouTube's performance by modified configs, shorts redirect and much more!

  1. // ==UserScript==
  2. // @name YouTube Web Tweaks
  3. // @version 4.1.8
  4. // @description This script optimizes YouTube's performance by modified configs, shorts redirect and much more!
  5. // @author Magma_Craft
  6. // @license MIT
  7. // @match *://www.youtube.com/*
  8. // @namespace https://greasyfork.org/en/users/933798
  9. // @icon https://www.youtube.com/favicon.ico
  10. // @unwrap
  11. // @run-at document-end
  12. // @unwrap
  13. // @grant none
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
  15. // ==/UserScript==
  16.  
  17. // Enable strict mode to catch common coding mistakes
  18. "use strict";
  19.  
  20. // Define the flags to assign to the EXPERIMENT_FLAGS object
  21. const flagsToAssign = {
  22. // Standard tweaks (YT config editor + Disable animations)
  23. IS_TABLET: true,
  24. DISABLE_YT_IMG_DELAY_LOADING: true,
  25. polymer_verifiy_app_state: false,
  26. desktop_delay_player_resizing: false,
  27. web_animated_actions: false,
  28. web_animated_like: false,
  29. web_animated_like_lazy_load: false,
  30. render_unicode_emojis_as_small_images: true,
  31. smartimation_background: false,
  32. kevlar_refresh_on_theme_change: false,
  33. // Disable cinematics (aka ambient lighting)
  34. kevlar_measure_ambient_mode_idle: false,
  35. kevlar_watch_cinematics_invisible: false,
  36. web_cinematic_theater_mode: false,
  37. web_cinematic_fullscreen: false,
  38. enable_cinematic_blur_desktop_loading: false,
  39. kevlar_watch_cinematics: false,
  40. web_cinematic_masthead: false,
  41. web_watch_cinematics_preferred_reduced_motion_default_disabled: false
  42. };
  43.  
  44. const updateFlags = () => {
  45. // Check if the EXPERIMENT_FLAGS object exists in the window.yt.config_ property chain
  46. const expFlags = window?.yt?.config_?.EXPERIMENT_FLAGS;
  47.  
  48. // If EXPERIMENT_FLAGS is not found, exit the function
  49. if (!expFlags) return;
  50.  
  51. // Assign the defined flags to the EXPERIMENT_FLAGS object
  52. Object.assign(expFlags, flagsToAssign);
  53. };
  54.  
  55. // Create a MutationObserver that calls the updateFlags function when changes occur in the document's subtree
  56. const mutationObserver = new MutationObserver(updateFlags);
  57. mutationObserver.observe(document, { subtree: true, childList: true });
  58.  
  59. // Other adjustments to be tweaked (Re-adding Explore tab, redirecting shorts to watch, wtc...)
  60. function waitForElm(selector) {
  61. return new Promise(resolve => {
  62. if (document.querySelector(selector)) {
  63. return resolve(document.querySelector(selector));
  64. }
  65.  
  66. const observer = new MutationObserver(mutations => {
  67. if (document.querySelector(selector)) {
  68. resolve(document.querySelector(selector));
  69. observer.disconnect();
  70. }
  71. });
  72.  
  73. observer.observe(document.body, {
  74. childList: true,
  75. subtree: true
  76. });
  77. });
  78. }
  79.  
  80. function restoreTrending() {
  81.  
  82. var trendingData = {
  83. "navigationEndpoint": {
  84. "clickTrackingParams": "CBwQtSwYASITCNqYh-qO_fACFcoRrQYdP44D9Q==",
  85. "commandMetadata": {
  86. "webCommandMetadata": {
  87. "url": "/feed/explore",
  88. "webPageType": "WEB_PAGE_TYPE_BROWSE",
  89. "rootVe": 6827,
  90. "apiUrl": "/youtubei/v1/browse"
  91. }
  92. },
  93. "browseEndpoint": {
  94. "browseId": "FEtrending"
  95. }
  96. },
  97. "icon": {
  98. "iconType": "EXPLORE"
  99. },
  100. "trackingParams": "CBwQtSwYASITCNqYh-qO_fACFcoRrQYdP44D9Q==",
  101. "formattedTitle": {
  102. "simpleText": "Explore"
  103. },
  104. "accessibility": {
  105. "accessibilityData": {
  106. "label": "Explore"
  107. }
  108. },
  109. "isPrimary": true
  110. };
  111.  
  112. var guidetemplate = `<ytd-guide-entry-renderer class="style-scope ytd-guide-section-renderer" is-primary="" line-end-style="none"><!--css-build:shady--><a id="endpoint" class="yt-simple-endpoint style-scope ytd-guide-entry-renderer" tabindex="-1" role="tablist"><tp-yt-paper-item role="tab" class="style-scope ytd-guide-entry-renderer" tabindex="0" aria-disabled="false"><!--css-build:shady--><yt-icon class="guide-icon style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><yt-img-shadow height="24" width="24" class="style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-img-shadow><yt-formatted-string class="title style-scope ytd-guide-entry-renderer"><!--css-build:shady--></yt-formatted-string><span class="guide-entry-count style-scope ytd-guide-entry-renderer"></span><yt-icon class="guide-entry-badge style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><div id="newness-dot" class="style-scope ytd-guide-entry-renderer"></div></tp-yt-paper-item></a><yt-interaction class="style-scope ytd-guide-entry-renderer"><!--css-build:shady--><div class="stroke style-scope yt-interaction"></div><div class="fill style-scope yt-interaction"></div></yt-interaction></ytd-guide-entry-renderer>`;
  113. document.querySelector(`#items > ytd-guide-entry-renderer:nth-child(2)`).data = trendingData;
  114.  
  115. var miniguidetemplate = `<ytd-mini-guide-entry-renderer class="style-scope ytd-mini-guide-section-renderer" is-primary="" line-end-style="none"><!--css-build:shady--><a id="endpoint" class="yt-simple-endpoint style-scope ytd-guide-entry-renderer" tabindex="-1" role="tablist"><tp-yt-paper-item role="tab" class="style-scope ytd-guide-entry-renderer" tabindex="0" aria-disabled="false"><!--css-build:shady--><yt-icon class="guide-icon style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><yt-img-shadow height="24" width="24" class="style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-img-shadow><yt-formatted-string class="title style-scope ytd-guide-entry-renderer"><!--css-build:shady--></yt-formatted-string><span class="guide-entry-count style-scope ytd-guide-entry-renderer"></span><yt-icon class="guide-entry-badge style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><div id="newness-dot" class="style-scope ytd-guide-entry-renderer"></div></tp-yt-paper-item></a><yt-interaction class="style-scope ytd-guide-entry-renderer"><!--css-build:shady--><div class="stroke style-scope yt-interaction"></div><div class="fill style-scope yt-interaction"></div></yt-interaction></ytd-guide-entry-renderer>`;
  116. document.querySelector(`#items > ytd-mini-guide-entry-renderer:nth-child(2)`).data = trendingData;
  117.  
  118. }
  119.  
  120.  
  121. waitForElm("#items.ytd-guide-section-renderer").then((elm) => {
  122. restoreTrending();
  123. });
  124.  
  125. waitForElm("#items.ytd-mini-guide-section-renderer").then((elm) => {
  126. restoreTrending();
  127. });
  128.  
  129.  
  130. (function() {
  131. let css = `
  132. /* Remove Shorts, Trending, Podcasts and Shopping buttons to make the sidebar less almost prior to late 2022 */
  133. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Shorts"],
  134. a.yt-simple-endpoint.style-scope.ytd-mini-guide-entry-renderer[title="Shorts"] {
  135. display: none !important;
  136. }
  137.  
  138. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Trending"] {
  139. display: none !important;
  140. }
  141.  
  142. #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Podcasts"],
  143. ytd-guide-entry-renderer > a[href*="/feed/podcasts"] {
  144. display: none !important
  145. }
  146.  
  147. ytd-guide-entry-renderer > a[href*="/channel/UCkYQyvc_i9hXEo4xic9Hh2g"] {
  148. display: none !important;
  149. }
  150.  
  151. /* Remove filter categories on search results and playlists to make the UI less usable on low-entry machines */
  152. ytd-item-section-renderer.style-scope.ytd-section-list-renderer[page-subtype="playlist"] > #header.ytd-item-section-renderer > ytd-feed-filter-chip-bar-renderer {
  153. display: none !important;
  154. }
  155.  
  156. div#chip-bar.style-scope.ytd-search-header-renderer > yt-chip-cloud-renderer.style-scope.ytd-search-header-renderer > div#container.style-scope.yt-chip-cloud-renderer {
  157. display: none !important;
  158. }
  159.  
  160. /* Remove (almost) all annoyances (excludes 'YT TV and Premium' banners) */
  161. ytd-action-companion-ad-renderer, ytd-display-ad-renderer, ytd-video-masthead-ad-advertiser-info-renderer, ytd-video-masthead-ad-primary-video-renderer, ytd-in-feed-ad-layout-renderer, ytd-ad-slot-renderer, yt-about-this-ad-renderer, yt-mealbar-promo-renderer, ytd-ad-slot-renderer, ytd-in-feed-ad-layout-renderer, .ytd-video-masthead-ad-v3-renderer, div#root.style-scope.ytd-display-ad-renderer.yt-simple-endpoint, div#sparkles-container.style-scope.ytd-promoted-sparkles-web-renderer, div#main-container.style-scope.ytd-promoted-video-renderer, div#player-ads.style-scope.ytd-watch-flexy, ad-slot-renderer, ytm-promoted-sparkles-web-renderer, masthead-ad, #masthead-ad, ytd-video-quality-promo-renderer {
  162. display: none !important
  163. }`;
  164. if (typeof GM_addStyle !== "undefined") {
  165. GM_addStyle(css);
  166. } else {
  167. let styleNode = document.createElement("style");
  168. styleNode.appendChild(document.createTextNode(css));
  169. (document.querySelector("head") || document.documentElement).appendChild(styleNode);
  170. }
  171. })();
  172.  
  173. var oldHref = document.location.href;
  174. if (window.location.href.indexOf('youtube.com/shorts') > -1) {
  175. window.location.replace(window.location.toString().replace('/shorts/', '/watch?v='));
  176. }
  177. window.onload = function() {
  178. var bodyList = document.querySelector("body")
  179. var observer = new MutationObserver(function(mutations) {
  180. mutations.forEach(function(mutation) {
  181. if (oldHref != document.location.href) {
  182. oldHref = document.location.href;
  183. console.log('location changed!');
  184. if (window.location.href.indexOf('youtube.com/shorts') > -1) {
  185. window.location.replace(window.location.toString().replace('/shorts/', '/watch?v='));
  186. }
  187. }
  188. });
  189. });
  190. var config = {
  191. childList: true,
  192. subtree: true
  193. };
  194. observer.observe(bodyList, config);
  195. };
  196. // Fully replace shorts links with regular videos
  197. /**
  198. * Shorts URL redirect.
  199. *
  200. * This is called on initial visit only. Successive navigations
  201. * are managed by modifying the YouTube Desktop application.
  202. */
  203. (function(){
  204. /** @type {string} */
  205. var path = window.location.pathname;
  206. if (0 == path.search("/shorts"))
  207. {
  208. // Extract the video ID from the shorts link and redirect.
  209. /** @type {string} */
  210. var id = path.replace(/\/|shorts|\?.*/g, "");
  211. window.location.replace("https://www.youtube.com/watch?v=" + id);
  212. }
  213. })();
  214. /**
  215. * YouTube Desktop Shorts remover.
  216. *
  217. * If the initial URL was not a shorts link, traditional redirection
  218. * will not work. This instead modifies video elements to replace them with
  219. * regular links.
  220. */
  221. (function(){
  222. /**
  223. * @param {string} selector (CSS-style) of the element
  224. * @return {Promise<Element>}
  225. */
  226. async function querySelectorAsync(selector)
  227. {
  228. while (null == document.querySelector(selector))
  229. {
  230. // Pause for a frame and let other code go on.
  231. await new Promise(r => requestAnimationFrame(r));
  232. }
  233. return document.querySelector(selector);
  234. }
  235. /**
  236. * Small toolset for interacting with the Polymer
  237. * YouTube Desktop application.
  238. *
  239. * @author Taniko Yamamoto <kirasicecreamm@gmail.com>
  240. * @version 1.0
  241. */
  242. class YtdTools
  243. {
  244. /** @type {string} Page data updated event */
  245. static EVT_DATA_UPDATE = "yt-page-data-updated";
  246. /** @type {Element} Main YT Polymer manager */
  247. static YtdApp;
  248. /** @type {bool} */
  249. static hasInitialLoaded = false;
  250. /** @return {Promise<bool>} */
  251. static async isPolymer()
  252. {
  253. /** @return {Promise<void>} */
  254. function waitForBody() // nice hack lazy ass
  255. {
  256. return new Promise(r => {
  257. document.addEventListener("DOMContentLoaded", function a(){
  258. document.removeEventListener("DOMContentLoaded", a);
  259. r();
  260. });
  261. });
  262. }
  263. await waitForBody();
  264. if ("undefined" != typeof document.querySelector("ytd-app"))
  265. {
  266. this.YtdApp = document.querySelector("ytd-app");
  267. return true;
  268. }
  269. return false;
  270. }
  271. /** @async @return {Promise<void|string>} */
  272. static waitForInitialLoad()
  273. {
  274. var updateEvent = this.EVT_DATA_UPDATE;
  275. return new Promise((resolve, reject) => {
  276. if (!this.isPolymer())
  277. {
  278. reject("Not Polymer :(");
  279. }
  280. function _listenerCb()
  281. {
  282. document.removeEventListener(updateEvent, _listenerCb);
  283. resolve();
  284. }
  285. document.addEventListener(updateEvent, _listenerCb);
  286. });
  287. }
  288. /** @return {string} */
  289. static getPageType()
  290. {
  291. return this.YtdApp.data.page;
  292. }
  293. }
  294. class ShortsTools
  295. {
  296. /** @type {MutationObserver} */
  297. static mo = new MutationObserver(muts => {
  298. muts.forEach(mut => {
  299. Array.from(mut.addedNodes).forEach(node => {
  300. if (node instanceof HTMLElement) {
  301. this.onMutation(node);
  302. }
  303. });
  304. });
  305. });
  306. /** @return {void} */
  307. static watchForShorts()
  308. {
  309. /*
  310. this.mo.observe(YtdTools.YtdApp, {
  311. childList: true,
  312. subtree: true
  313. });
  314. */
  315. var me = this;
  316. YtdTools.YtdApp.arrive("ytd-video-renderer, ytd-grid-video-renderer", function() {
  317. me.onMutation(this);
  318. // This is literally the worst hack I ever wrote, but it works ig...
  319. (new MutationObserver(function(){
  320. if (me.isShortsRenderer(this))
  321. {
  322. me.onMutation(this);
  323. }
  324. }.bind(this))).observe(this, {"subtree": true, "childList": true, "characterData": "true"});
  325. });
  326. }
  327. /** @return {void} */
  328. static stopWatchingForShorts()
  329. {
  330. this.mo.disconnect();
  331. }
  332. /**
  333. * @param {HTMLElement} node
  334. * @return {void}
  335. */
  336. static onMutation(node)
  337. {
  338. if (node.tagName.search("VIDEO-RENDERER") > -1 && this.isShortsRenderer(node))
  339. {
  340. this.transformShortsRenderer(node);
  341. }
  342. }
  343. /** @return {bool} */
  344. static isShortsRenderer(videoRenderer)
  345. {
  346. return "WEB_PAGE_TYPE_SHORTS" == videoRenderer?.data?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.webPageType;
  347. }
  348. /** @return {string} */
  349. static extractLengthFromA11y(videoData)
  350. {
  351. // A11y = {title} by {creator} {date} {*length*} {viewCount} - play Short
  352. // tho hopefully this works in more than just English
  353. var a11yTitle = videoData.title.accessibility.accessibilityData.label;
  354. var publishedTimeText = videoData.publishedTimeText.simpleText;
  355. var viewCountText = videoData.viewCountText.simpleText;
  356. var isolatedLengthStr = a11yTitle.split(publishedTimeText)[1].split(viewCountText)[0]
  357. .replace(/\s/g, "");
  358. var numbers = isolatedLengthStr.split(/\D/g);
  359. var string = "";
  360. // Remove all empties before iterating it
  361. for (var i = 0; i < numbers.length; i++)
  362. {
  363. if ("" === numbers[i])
  364. {
  365. numbers.splice(i, 1);
  366. i--;
  367. }
  368. }
  369. for (var i = 0; i < numbers.length; i++)
  370. {
  371. // Lazy 0 handling idc im tired
  372. if (1 == numbers.length)
  373. {
  374. string += "0:";
  375. if (1 == numbers[i].length)
  376. {
  377. string += "0" + numbers[i];
  378. }
  379. else
  380. {
  381. string += numbers[i];
  382. }
  383. break;
  384. }
  385. if (0 != i) string += ":";
  386. if (0 != i && 1 == numbers[i].length) string += "0";
  387. string += numbers[i];
  388. }
  389. return string;
  390. }
  391. /**
  392. * @param {HTMLElement} videoRenderer
  393. * @return {void}
  394. */
  395. static transformShortsRenderer(videoRenderer)
  396. {
  397. /** @type {string} */
  398. var originalOuterHTML = videoRenderer.outerHTML;
  399. /** @type {string} */
  400. var lengthText = videoRenderer.data?.lengthText?.simpleText ?? this.extractLengthFromA11y(videoRenderer.data);
  401. /** @type {string} */
  402. var lengthA11y = videoRenderer.data?.lengthText?.accessibility?.accessibilityData?.label ?? "";
  403. /** @type {string} */
  404. var originalHref = videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.url;
  405. var href = "/watch?v=" + originalHref.replace(/\/|shorts|\?.*/g, "");
  406. var reelWatchEndpoint = videoRenderer.data.navigationEndpoint.reelWatchEndpoint;
  407. var i;
  408. videoRenderer.data.thumbnailOverlays.forEach((a, index) =>{
  409. if ("thumbnailOverlayTimeStatusRenderer" in a)
  410. {
  411. i = index;
  412. }
  413. });
  414. // Set the thumbnail overlay style
  415. videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.style = "DEFAULT";
  416. delete videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.icon;
  417. // Set the thumbnail overlay text
  418. videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.text.simpleText = lengthText;
  419. // Set the thumbnail overlay accessibility label
  420. videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.text.accessibility.accessibilityData.label = lengthA11y;
  421. // Set the navigation endpoint metadata (used for middle click)
  422. videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.webPageType = "WEB_PAGE_TYPE_WATCH";
  423. videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.url = href;
  424. videoRenderer.data.navigationEndpoint.watchEndpoint = {
  425. "videoId": reelWatchEndpoint.videoId,
  426. "playerParams": reelWatchEndpoint.playerParams,
  427. "params": reelWatchEndpoint.params
  428. };
  429. delete videoRenderer.data.navigationEndpoint.reelWatchEndpoint;
  430. //var _ = videoRenderer.data; videoRenderer.data = {}; videoRenderer.data = _;
  431. // Sometimes the old school data cycle trick fails,
  432. // however this always works.
  433. var _ = videoRenderer.cloneNode();
  434. _.data = videoRenderer.data;
  435. for (var i in videoRenderer.properties)
  436. {
  437. _[i] = videoRenderer[i];
  438. }
  439. videoRenderer.insertAdjacentElement("afterend", _);
  440. videoRenderer.remove();
  441. }
  442. }
  443. /**
  444. * Sometimes elements are reused on page updates, so fix that
  445. *
  446. * @return {void}
  447. */
  448. function onDataUpdate()
  449. {
  450. var videos = document.querySelectorAll("ytd-video-renderer, ytd-grid-video-renderer");
  451. for (var i = 0, l = videos.length; i < l; i++) if (ShortsTools.isShortsRenderer(videos[i]))
  452. {
  453. ShortsTools.transformShortsRenderer(videos[i]);
  454. }
  455. }
  456. /**
  457. * I hope she makes lotsa spaghetti :D
  458. * @async @return {Promise<void>}
  459. */
  460. async function main()
  461. {
  462. // If not Polymer, nothing happens
  463. if (await YtdTools.isPolymer())
  464. {
  465. ShortsTools.watchForShorts();
  466. document.addEventListener("yt-page-data-updated", onDataUpdate);
  467. }
  468. }
  469. main();
  470. })();
  471.  
  472. // Auto skip ads and force disable autopause (special thanks to CY Fung for the ads skip script code
  473. (() => {
  474. let popupState = 0;
  475. let popupElement = null;
  476. const rate = 1;
  477. const Promise = (async () => { })().constructor;
  478. const PromiseExternal = ((resolve_, reject_) => {
  479. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  480. return class PromiseExternal extends Promise {
  481. constructor(cb = h) {
  482. super(cb);
  483. if (cb === h) {
  484. /** @type {(value: any) => void} */
  485. this.resolve = resolve_;
  486. /** @type {(reason?: any) => void} */
  487. this.reject = reject_;
  488. }
  489. }
  490. };
  491. })();
  492. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  493. let vload = null;
  494. const fastSeekFn = HTMLVideoElement.prototype.fastSeek || null;
  495. const addEventListenerFn = HTMLElement.prototype.addEventListener;
  496. if (!addEventListenerFn) return;
  497. const removeEventListenerFn = HTMLElement.prototype.removeEventListener;
  498. if (!removeEventListenerFn) return;
  499. const ytPremiumPopupSelector = 'yt-mealbar-promo-renderer.style-scope.ytd-popup-container:not([hidden])';
  500. const DEBUG = 0;
  501. const rand = (a, b) => a + Math.random() * (b - a);
  502. const log = DEBUG ? console.log.bind(console) : () => 0;
  503. //$0.$['dismiss-button'].click()
  504. const ytPremiumPopupClose = function () {
  505. const popup = document.querySelector(ytPremiumPopupSelector);
  506. if (popup instanceof HTMLElement) {
  507. if (HTMLElement.prototype.closest.call(popup, '[hidden]')) return;
  508. const cnt = insp(popup);
  509. const btn = cnt.$ ? cnt.$['dismiss-button'] : 0;
  510. if (btn instanceof HTMLElement && HTMLElement.prototype.closest.call(btn, '[hidden]')) return;
  511. btn && btn.click();
  512. }
  513. }
  514. //div.video-ads.ytp-ad-module
  515. const clickSkip = function () {
  516. // ytp-ad-skip-button
  517. const isAdsContainerContainsButton = document.querySelector('.video-ads.ytp-ad-module button');
  518. if (isAdsContainerContainsButton) {
  519. const btnFilter = e => HTMLElement.prototype.matches.call(e, ".ytp-ad-overlay-close-button, .ytp-ad-skip-button-modern, .ytp-ad-skip-button") && !HTMLElement.prototype.closest.call(e, '[hidden]');
  520. const btns = [...document.querySelectorAll('.video-ads.ytp-ad-module button[class*="ytp-ad-"]')].filter(btnFilter);
  521. console.log('# of ads skip btns', btns.length);
  522. if (btns.length !== 1) return;
  523. const btn = btns[0];
  524. if (btn instanceof HTMLElement) {
  525. btn.click();
  526. }
  527. }
  528. };
  529. const adsEndHandlerHolder = function (evt) {
  530. adsEndHandler && adsEndHandler(evt);
  531. }
  532. let adsEndHandler = null;
  533. const videoPlayingHandler = async function (evt) {
  534. try {
  535. if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return;
  536. const video = evt.target;
  537. const checkPopup = popupState === 1;
  538. popupState = 0;
  539. const popupElementValue = popupElement;
  540. popupElement = null;
  541. if (video.duration < 0.8) return;
  542. await vload.then();
  543. if (!video.isConnected) return;
  544. const ytplayer = HTMLElement.prototype.closest.call(video, 'ytd-player, ytmusic-player');
  545. if (!ytplayer || !ytplayer.is) return;
  546. const ytplayerCnt = insp(ytplayer);
  547. const player_ = await (ytplayerCnt.player_ || ytplayer.player_ || ytplayerCnt.playerApi || ytplayer.playerApi || 0);
  548. if (!player_) return;
  549. if (typeof ytplayerCnt.getPlayer === 'function' && !ytplayerCnt.getPlayer()) {
  550. await new Promise(r => setTimeout(r, 40));
  551. }
  552. const playerController = await ytplayerCnt.getPlayer() || player_;
  553. if (!video.isConnected) return;
  554. if ('getPresentingPlayerType' in playerController && 'getDuration' in playerController) {
  555. const ppType = await playerController.getPresentingPlayerType();
  556. log('m02a', ppType);
  557. if (ppType === 1 || typeof ppType !== 'number') return; // ads shall be ppType === 2
  558. // const progressState = player_.getProgressState();
  559. // log('m02b', progressState);
  560. // if(!progressState) return;
  561. // const q = progressState.duration;
  562. // if (popupState === 1) console.debug('m05b:ytPremiumPopup', document.querySelector(ytPremiumPopupSelector))
  563. const q = video.duration;
  564. const ytDuration = await playerController.getDuration();
  565. log('m02c', q, ytDuration, Math.abs(ytDuration - q));
  566. if (q > 0.8 && ytDuration > 2.5 && Math.abs(ytDuration - q) > 1.4) {
  567. try {
  568. log('m02s', 'fastSeek', q);
  569. video.muted = true;
  570. const w = Math.round(rand(582, 637) * rate);
  571. const sq = q - w / 1000;
  572. adsEndHandler = null;
  573. const expired = Date.now() + 968;
  574. removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  575. removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  576. removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  577. addEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  578. addEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  579. addEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  580. adsEndHandler = async function (evt) {
  581. adsEndHandler = null;
  582. removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  583. removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  584. removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  585. if (Date.now() < expired) {
  586. const delay = Math.round(rand(92, 117));
  587. await new Promise(r => setTimeout(r, delay));
  588. Promise.resolve().then(() => {
  589. clickSkip();
  590. }).catch(console.warn);
  591. checkPopup && Promise.resolve().then(() => {
  592. const currentPopup = document.querySelector(ytPremiumPopupSelector);
  593. if (popupElementValue ? currentPopup === popupElementValue : currentPopup) {
  594. ytPremiumPopupClose();
  595. }
  596. }).catch(console.warn);
  597. }
  598. };
  599. if (fastSeekFn) fastSeekFn.call(video, sq);
  600. else video.currentTime = sq;
  601. } catch (e) {
  602. console.warn(e);
  603. }
  604. }
  605. }
  606. } catch (e) {
  607. console.warn(e);
  608. }
  609. };
  610. document.addEventListener('loadedmetadata', async function (evt) {
  611. try {
  612. if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return;
  613. const video = evt.target;
  614. if (video.nodeName !== "VIDEO") return;
  615. if (video.duration < 0.8) return;
  616. if (!video.matches('.video-stream.html5-main-video')) return;
  617. popupState = 0;
  618. vload = new PromiseExternal();
  619. popupElement = document.querySelector(ytPremiumPopupSelector);
  620. removeEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false });
  621. addEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false });
  622. popupState = 1;
  623. let trial = 6;
  624. await new Promise(resolve => {
  625. let io = new IntersectionObserver(entries => {
  626. if (trial-- <= 0 || (entries && entries.length >= 1 && video.matches('ytd-player video, ytmusic-player video'))) {
  627. resolve();
  628. io.disconnect();
  629. io = null;
  630. }
  631. });
  632. io.observe(video);
  633. });
  634. vload.resolve();
  635. } catch (e) {
  636. console.warn(e);
  637. }
  638. }, true);
  639. })();
  640.  
  641. Object.defineProperties(document, { /*'hidden': {value: false},*/ 'webkitHidden': {value: false}, 'visibilityState': {value: 'visible'}, 'webkitVisibilityState': {value: 'visible'} });
  642.  
  643. setInterval(function(){
  644. document.dispatchEvent( new KeyboardEvent( 'keyup', { bubbles: true, cancelable: true, keyCode: 143, which: 143 } ) );
  645. }, 60000);