YouTube: Make AutoPlay Next More Than 3 seconds

To make AutoPlay Next Duration longer

  1. // ==UserScript==
  2. // @name YouTube: Make AutoPlay Next More Than 3 seconds
  3. // @namespace UserScripts
  4. // @match https://www.youtube.com/*
  5. // @version 0.2.9
  6. // @author CY Fung
  7. // @license MIT
  8. // @description To make AutoPlay Next Duration longer
  9. // @grant none
  10. // @run-at document-start
  11. // @unwrap
  12. // @inject-into page
  13. // ==/UserScript==
  14.  
  15. (() => {
  16.  
  17. const second_to_play_next_def = 8;
  18.  
  19. const valuer = {
  20. get second_to_play_next() {
  21. return +localStorage.second_to_play_next || second_to_play_next_def;
  22. }
  23. }
  24.  
  25. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  26.  
  27. const Promise = (async () => { })().constructor;
  28.  
  29. /*
  30. *
  31. , $mb = function(a, b) {
  32. b = void 0 === b ? -1 : b;
  33. a = a.j.Ha("ytp-autonav-endscreen-upnext-header");
  34. g.of(a);
  35. if (0 <= b) {
  36. b = String(b);
  37. var c = "$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".match(RegExp("\\$SECONDS", "gi"))[0]
  38. , d = "$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".indexOf(c);
  39. if (0 <= d) {
  40. a.appendChild(g.mf("$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".slice(0, d)));
  41. var e = g.lf("span");
  42. g.$t(e, "ytp-autonav-endscreen-upnext-header-countdown-number");
  43. g.Bf(e, b);
  44. a.appendChild(e);
  45. a.appendChild(g.mf("$SECONDS \u79d2\u5f8c\u306b\u6b21\u306e\u52d5\u753b\u3092\u518d\u751f".slice(d + c.length)));
  46. return
  47. }
  48. }
  49. g.Bf(a, "\u6b21\u306e\u52d5\u753b")
  50. }
  51. */
  52.  
  53.  
  54.  
  55.  
  56.  
  57. /*
  58. *
  59. if (!a.Qk()) {
  60. a.J.Bf() ? $mb(a, Math.round(anb(a) / 1E3)) : $mb(a);
  61. b = !!a.suggestion && !!a.suggestion.Bs;
  62. var c = a.J.Bf() || !b;
  63. g.fu(a.container.element, "ytp-autonav-endscreen-upnext-alternative-header-only", !c && b);
  64. g.fu(a.container.element, "ytp-autonav-endscreen-upnext-no-alternative-header", c && !b);
  65. g.FG(a.B, a.J.Bf());
  66. g.fu(a.element, "ytp-enable-w2w-color-transitions", bnb(a))
  67. }
  68. */
  69.  
  70. /*
  71. *
  72. *
  73. , anb = function(a) {
  74. if (a.J.isFullscreen()) {
  75. var b;
  76. a = null == (b = a.J.getVideoData()) ? void 0 : b.CB;
  77. return -1 === a || void 0 === a ? 8E3 : a
  78. }
  79. return 0 <= a.J.Ss() ? a.J.Ss() : g.WJ(a.J.W().experiments, "autoplay_time") || 1E4
  80. }
  81. */
  82.  
  83. // a.J instanceof g.AU
  84. /*
  85. *
  86. *
  87. g.AU {Dp: false, xk: undefined, app: g.b1, state: WQa, playerType: undefined, …}
  88. Dp: false
  89. UK: pYa {Dp: false, xk: Array(6), Ub: {…}, Td: {…}, element: div#ytp-id-18.ytp-popup.ytp-settings-menu, …}
  90. app: g.b1 {Dp: false, xk: Array(21), logger: g.pY, di: false, bA: false, …}
  91. element: null
  92. j: false
  93. playerType: undefined
  94. state: WQa {Dp: false, xk: undefined, D: Set(89), G: {…}, S: {…}, …}
  95. xk: undefined
  96. */
  97.  
  98. // a.J.Ss = ƒ (){return this.app.Ss()}
  99. // a.J.app.Ss = ƒ (){return this.getVideoData().aU}
  100. // a.J.app.getVideoData() = ƒ (){return this.Qb.getVideoData()} = object instanceof g.sT
  101. // a.J.app.Qb.getVideoData = ƒ (){return this.videoData}
  102. // g.sT.prototype
  103.  
  104. /*
  105. *
  106. getAudioTrack
  107. getAvailableAudioTracks
  108. getHeartbeatResponse
  109. getPlayerResponse
  110. getPlaylistSequenceForTime
  111. getStoryboardFormat
  112. hasProgressBarBoundaries
  113. hasSupportedAudio51Tracks
  114. isAd
  115. isDaiEnabled
  116. isLoaded
  117. isOtf
  118. useInnertubeDrmService
  119. */
  120.  
  121.  
  122.  
  123. const getsT = (_yt_player) => {
  124. const w = 'sT';
  125.  
  126. let arr = [];
  127.  
  128. for (const [k, v] of Object.entries(_yt_player)) {
  129.  
  130. const p = typeof v === 'function' ? v.prototype : 0;
  131. if (p) {
  132. let q = 0;
  133.  
  134. if (typeof p.isLoaded === 'function' && p.isLoaded.length === 0) q += 200;
  135. if (q < 200) continue; // p.isLoaded is required
  136.  
  137. if (typeof p.hasSupportedAudio51Tracks === 'function' && p.hasSupportedAudio51Tracks.length === 0) q += 2;
  138. if (typeof p.getStoryboardFormat === 'function' && p.getStoryboardFormat.length === 0) q += 4;
  139. if (typeof p.getPlaylistSequenceForTime === 'function' && p.getPlaylistSequenceForTime.length === 1) q += 4;
  140.  
  141. if (typeof p.isOtf === 'function' && p.isOtf.length === 0) q += 2;
  142. if (typeof p.getAvailableAudioTracks === 'function' && p.getAvailableAudioTracks.length === 0) q += 4;
  143. if (typeof p.getAudioTrack === 'function' && p.getAudioTrack.length === 0) q += 4;
  144. if (typeof p.getPlayerResponse === 'function' && p.getPlayerResponse.length === 0) q += 2;
  145. if (typeof p.getHeartbeatResponse === 'function' && p.getHeartbeatResponse.length === 0) q += 2;
  146.  
  147. if (typeof p.isAd === 'function' && p.isAd.length === 0) q += 2;
  148. if (typeof p.isDaiEnabled === 'function' && p.isDaiEnabled.length === 1) q += 2;
  149. if (typeof p.useInnertubeDrmService === 'function' && p.useInnertubeDrmService.length === 0) q++;
  150. if (typeof p.hasProgressBarBoundaries === 'function' && p.hasProgressBarBoundaries.length === 0) q += 2;
  151.  
  152. if (q > 0) arr.push([k, q]);
  153.  
  154. }
  155.  
  156. }
  157.  
  158.  
  159. if (arr.length === 0) {
  160.  
  161. console.warn(`Key does not exist. [${w}]`);
  162. } else {
  163.  
  164. if (arr.length > 1) arr.sort((a, b) => b[1] - a[1]);
  165.  
  166.  
  167. console.log(`[${w}]`, arr);
  168. return arr[0][0];
  169. }
  170.  
  171.  
  172.  
  173. }
  174.  
  175.  
  176. // const addProtoToArr = (parent, key, arr) => {
  177.  
  178.  
  179. // let isChildProto = false;
  180. // for (const sr of arr) {
  181. // if (parent[key].prototype instanceof parent[sr]) {
  182. // isChildProto = true;
  183. // break;
  184. // }
  185. // }
  186.  
  187. // if (isChildProto) return;
  188.  
  189. // arr = arr.filter(sr => {
  190. // if (parent[sr].prototype instanceof parent[key]) {
  191. // return false;
  192. // }
  193. // return true;
  194. // });
  195.  
  196. // arr.push(key);
  197.  
  198. // return arr;
  199.  
  200.  
  201. // }
  202.  
  203.  
  204. // const getAU = (_yt_player) => {
  205. // const w = 'VG';
  206.  
  207. // let arr = [];
  208.  
  209. // for (const [k, v] of Object.entries(_yt_player)) {
  210.  
  211. // const p = typeof v === 'function' ? v.prototype : 0;
  212. // if (p
  213. // && typeof p.show === 'function' && p.show.length === 1
  214. // && typeof p.hide === 'function' && p.hide.length === 0
  215. // && typeof p.stop === 'function' && p.stop.length === 0) {
  216.  
  217. // arr = addProtoToArr(_yt_player, k, arr) || arr;
  218.  
  219. // }
  220.  
  221. // }
  222.  
  223.  
  224. // if (arr.length === 0) {
  225.  
  226. // console.warn(`Key does not exist. [${w}]`);
  227. // } else {
  228.  
  229. // console.log(`[${w}]`, arr);
  230. // return arr[0];
  231. // }
  232.  
  233.  
  234.  
  235. // }
  236.  
  237.  
  238.  
  239. const cleanContext = async (win) => {
  240. const waitFn = requestAnimationFrame; // shall have been binded to window
  241. try {
  242. let mx = 16; // MAX TRIAL
  243. const frameId = 'vanillajs-iframe-v1';
  244. /** @type {HTMLIFrameElement | null} */
  245. let frame = document.getElementById(frameId);
  246. let removeIframeFn = null;
  247. if (!frame) {
  248. frame = document.createElement('iframe');
  249. frame.id = frameId;
  250. const blobURL = typeof webkitCancelAnimationFrame === 'function' && typeof kagi === 'undefined' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
  251. frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
  252. let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
  253. n.appendChild(frame);
  254. while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
  255. const root = document.documentElement;
  256. root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
  257. if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));
  258.  
  259. removeIframeFn = (setTimeout) => {
  260. const removeIframeOnDocumentReady = (e) => {
  261. e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  262. win = null;
  263. const m = n;
  264. n = null;
  265. setTimeout(() => m.remove(), 200);
  266. }
  267. if (document.readyState !== 'loading') {
  268. removeIframeOnDocumentReady();
  269. } else {
  270. win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
  271. }
  272. }
  273. }
  274. while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
  275. const fc = frame.contentWindow;
  276. if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
  277. const { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle } = fc;
  278. const res = { requestAnimationFrame, setTimeout, cancelAnimationFrame, setInterval, clearInterval, requestIdleCallback, getComputedStyle };
  279. for (let k in res) res[k] = res[k].bind(win); // necessary
  280. if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
  281. res.animate = fc.HTMLElement.prototype.animate;
  282. return res;
  283. } catch (e) {
  284. console.warn(e);
  285. return null;
  286. }
  287. };
  288.  
  289.  
  290.  
  291. cleanContext(window).then(__CONTEXT__ => {
  292. if (!__CONTEXT__) return null;
  293.  
  294. const { setTimeout } = __CONTEXT__;
  295.  
  296. const isUrlInEmbed = location.href.includes('.youtube.com/embed/');
  297. const isAbortSignalSupported = typeof AbortSignal !== "undefined";
  298.  
  299. const promiseForTamerTimeout = new Promise(resolve => {
  300. !isUrlInEmbed && isAbortSignalSupported && document.addEventListener('yt-action', function () {
  301. setTimeout(resolve, 480);
  302. }, { capture: true, passive: true, once: true });
  303. !isUrlInEmbed && isAbortSignalSupported && typeof customElements === "object" && customElements.whenDefined('ytd-app').then(() => {
  304. setTimeout(resolve, 1200);
  305. });
  306. setTimeout(resolve, 3000);
  307. });
  308.  
  309.  
  310. const PromiseExternal = ((resolve_, reject_) => {
  311. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  312. return class PromiseExternal extends Promise {
  313. constructor(cb = h) {
  314. super(cb);
  315. if (cb === h) {
  316. /** @type {(value: any) => void} */
  317. this.resolve = resolve_;
  318. /** @type {(reason?: any) => void} */
  319. this.reject = reject_;
  320. }
  321. }
  322. };
  323. })();
  324.  
  325. (async () => {
  326.  
  327. const autoplayRendererP = new PromiseExternal();
  328. const instReadyP = new PromiseExternal();
  329.  
  330. let bSetupDone = false;
  331.  
  332. const observablePromise = (proc, timeoutPromise) => {
  333. let promise = null;
  334. return {
  335. obtain() {
  336. if (!promise) {
  337. promise = new Promise(resolve => {
  338. let mo = null;
  339. const f = () => {
  340. let t = proc();
  341. if (t) {
  342. mo.disconnect();
  343. mo.takeRecords();
  344. mo = null;
  345. resolve(t);
  346. }
  347. }
  348. mo = new MutationObserver(f);
  349. mo.observe(document, { subtree: true, childList: true })
  350. f();
  351. timeoutPromise && timeoutPromise.then(() => {
  352. resolve(null)
  353. });
  354. });
  355. }
  356. return promise
  357. }
  358. }
  359. }
  360.  
  361. if (isAbortSignalSupported && !isUrlInEmbed) {
  362. await customElements.whenDefined('ytd-player');
  363. }
  364.  
  365. const _yt_player = await observablePromise(() => {
  366. return (((window || 0)._yt_player || 0) || 0);
  367. }, promiseForTamerTimeout).obtain();
  368.  
  369. if (!_yt_player || typeof _yt_player !== 'object') return;
  370.  
  371. // store and control variables
  372. const pm = new Proxy({}, {
  373. set(target, prop, value, receiver) {
  374. let old = target[prop];
  375. if (old !== value) {
  376. target[prop] = old = value;
  377. if (prop === 'instsT') value && objProceed(true);
  378. }
  379. }
  380. });
  381.  
  382. window.set_second_to_play_next = function (sec) {
  383. if (!arguments.length) return +localStorage.second_to_play_next;
  384. localStorage.second_to_play_next = `${sec}`;
  385. assignDurationToOAR(pm.playerOAR);
  386. console.log('The value will be applied to the next video');
  387. }
  388.  
  389. const objProceed = (toResolveInstSetPromise) => {
  390. const resAssigned = obtainOAR();
  391. if (resAssigned) bSetupDone ? assignDurationToOAR(pm.playerOAR) : autoplayRendererP.resolve();
  392. if (toResolveInstSetPromise) instReadyP.resolve();
  393. }
  394.  
  395. const assignDurationToOAR = (playerOAR) => {
  396. if (!playerOAR) return;
  397. const t4 = playerOAR.countDownSecsForFullscreen;
  398. const t5 = playerOAR.countDownSecs;
  399. let b = t4 === t5
  400. const second_to_play_next = valuer.second_to_play_next;
  401. playerOAR.countDownSecsForFullscreen = second_to_play_next;
  402. if (b) playerOAR.countDownSecs = second_to_play_next;
  403. return b;
  404. }
  405.  
  406. Promise.all([autoplayRendererP, instReadyP]).then(() => {
  407.  
  408. const inst = pm.instsT;
  409. const playerOAR = pm.playerOAR;
  410. if (!inst || !playerOAR) return;
  411.  
  412. let entriesA = Object.entries(inst).filter(e => typeof e[1] === 'number');
  413. let m = new Map();
  414. for (const entry of entriesA) {
  415. m.set(entry[0], entry[1]);
  416. }
  417.  
  418. const second_to_play_next = valuer.second_to_play_next;
  419.  
  420. let b = assignDurationToOAR(playerOAR);
  421.  
  422. bSetupDone = true;
  423.  
  424. setTimeout(() => {
  425. const entriesB = Object.entries(inst).filter(e => typeof e[1] === 'number' && m.get(e[0]) !== e[1]);
  426. m = null;
  427. const filtered = entriesB.filter(e => Math.abs(e[1] - second_to_play_next * 1E3) < 1e-8);
  428. if (filtered.length >= 1) {
  429. pm.targetKeys = filtered.map(e => e[0]);
  430. } else {
  431. pm.targetKeys = null;
  432. }
  433. console.log(`sT(${b ? 'T' : 'F'}):`, filtered, filtered.length)
  434. }, 80);
  435.  
  436. });
  437.  
  438. const obtainOAR = () => {
  439.  
  440. let playerOAR = null;
  441. let pageDataMgr = document.querySelector('ytd-page-manager#page-manager');
  442. let pageData = pageDataMgr ? insp(pageDataMgr).data : null;
  443. if (pageData) {
  444. try {
  445. playerOAR = pageData.response.playerOverlays.playerOverlayRenderer.autoplay.playerOverlayAutoplayRenderer;
  446. } catch (e) { }
  447. }
  448. if (playerOAR && typeof playerOAR.countDownSecsForFullscreen === 'number' && playerOAR.countDownSecsForFullscreen < 15) {
  449. pm.playerOAR = playerOAR;
  450. return true;
  451. }
  452.  
  453. };
  454.  
  455. document.addEventListener('yt-navigate-finish', function () {
  456. objProceed(false);
  457. }, false);
  458.  
  459. const g = _yt_player;
  460. const keysT = getsT(_yt_player);
  461.  
  462. if (keysT) {
  463. let k = keysT;
  464. let gk = g[k];
  465. let gkp = g[k].prototype;
  466.  
  467.  
  468. /*
  469. *
  470. if (typeof p.hasSupportedAudio51Tracks === 'function' && p.hasSupportedAudio51Tracks.length === 0) q += 2;
  471. if (typeof p.getStoryboardFormat === 'function' && p.getStoryboardFormat.length === 0) q += 4;
  472. if (typeof p.getPlaylistSequenceForTime === 'function' && p.getPlaylistSequenceForTime.length === 1) q += 4;
  473. if (typeof p.isLoaded === 'function' && p.isLoaded.length === 0) q += 2;
  474. if (typeof p.isOtf === 'function' && p.isOtf.length === 0) q += 2;
  475. if (typeof p.getAvailableAudioTracks === 'function' && p.getAvailableAudioTracks.length === 0) q += 4;
  476. if (typeof p.getAudioTrack === 'function' && p.getAudioTrack.length === 0) q += 4;
  477. if (typeof p.getPlayerResponse === 'function' && p.getPlayerResponse.length === 0) q += 2;
  478. if (typeof p.getHeartbeatResponse === 'function' && p.getHeartbeatResponse.length === 0) q += 2;
  479. if (typeof p.isAd === 'function' && p.isAd.length === 0) q += 2;
  480. if (typeof p.isDaiEnabled === 'function' && p.isDaiEnabled.length === 1) q += 2;
  481. if (typeof p.useInnertubeDrmService === 'function' && p.useInnertubeDrmService.length === 0) q++;
  482. if (typeof p.hasProgressBarBoundaries === 'function' && p.hasProgressBarBoundaries.length === 0) q += 2;
  483. */
  484.  
  485. // for(const pk of ['hasSupportedAudio51Tracks','getStoryboardFormat','getPlaylistSequenceForTime','isLoaded',
  486. // 'isOtf',//'getAvailableAudioTracks','getAudioTrack',
  487. // 'getPlayerResponse','getHeartbeatResponse',
  488. // // 'isAd',
  489. // 'isDaiEnabled','useInnertubeDrmService',
  490. // //'hasProgressBarBoundaries'
  491. // ]){
  492.  
  493. // }
  494.  
  495.  
  496. if (!gkp.isLoaded75 && typeof gkp.isLoaded === 'function') {
  497. gkp.isLoaded75 = gkp.isLoaded;
  498. gkp.isLoaded = function () {
  499. pm.instsT = this;
  500. return arguments.length === 0 ? this.isLoaded75() : this.isLoaded75(...arguments);
  501. }
  502. }
  503.  
  504. }
  505.  
  506.  
  507.  
  508.  
  509.  
  510. })();
  511.  
  512. });
  513.  
  514. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址