YouTube Web Tweaks

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

目前为 2024-11-12 提交的版本,查看 最新版本

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