YouTube: Single Column Tamer

Re-adoption of Single Column Detection against video and browser sizes

  1. // ==UserScript==
  2. // @name YouTube: Single Column Tamer
  3. // @namespace UserScripts
  4. // @match https://www.youtube.com/*
  5. // @grant none
  6. // @unwrap
  7. // @inject-into page
  8. // @version 0.1.8
  9. // @author CY Fung
  10. // @description Re-adoption of Single Column Detection against video and browser sizes
  11. // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@8fac46500c5a916e6ed21149f6c25f8d1c56a6a3/library/ytZara.js
  12. // @require https://update.gf.qytechs.cn/scripts/475632/1361351/ytConfigHacks.js
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (() => {
  17. const ENABLE_WHEN_CONTENT_OCCUPY_MORE_THAN = 0.2 // 20% or more of other content can be displayed in your browser
  18.  
  19. // protait screen & vertical live
  20.  
  21. let _isSingleColumnPreferred = false;
  22. let bypass = false;
  23. let videoRatio = null;
  24. let _forceTwoCols = 0;
  25. let cachedSCUsage = null;
  26.  
  27. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  28.  
  29. const Promise = (async () => { })().constructor;
  30.  
  31. const createPipeline = () => {
  32. let pipelineMutex = Promise.resolve();
  33. const pipelineExecution = fn => {
  34. return new Promise((resolve, reject) => {
  35. pipelineMutex = pipelineMutex.then(async () => {
  36. let res;
  37. try {
  38. res = await fn();
  39. } catch (e) {
  40. console.log('error_F1', e);
  41. reject(e);
  42. }
  43. resolve(res);
  44. }).catch(console.warn);
  45. });
  46. };
  47. return pipelineExecution;
  48. };
  49.  
  50. let rafPromise = null;
  51. const rafFn = (typeof webkitRequestAnimationFrame !== 'undefined' ? webkitRequestAnimationFrame : requestAnimationFrame).bind(window); // eslint-disable-line no-undef, no-constant-condition
  52.  
  53. const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
  54. rafFn(hRes => {
  55. rafPromise = null;
  56. resolve(hRes);
  57. });
  58. }));
  59.  
  60. const getProto = (element) => {
  61. if (element) {
  62. const cnt = insp(element);
  63. return cnt.constructor.prototype || null;
  64. }
  65. return null;
  66. };
  67.  
  68. function toQueryForcedTwoCols(q) {
  69. if (q && typeof q === 'string') {
  70. q = q.replace('1000px', '200.2px');
  71. q = q.replace('629px', '129.2px');
  72. q = q.replace('657px', '157.2px');
  73. q = q.replace('630px', '130.2px');
  74. q = q.replace('1327px', '237.2px');
  75. }
  76. return q;
  77. }
  78.  
  79. function toQueryForcedOneCol(q) {
  80. if (q && typeof q === 'string') {
  81. q = q.replace('1000px', '998200.3px');
  82. q = q.replace('629px', '998129.3px');
  83. q = q.replace('657px', '998157.3px');
  84. q = q.replace('630px', '998130.3px');
  85. q = q.replace('1327px', '998237.3px');
  86. }
  87. return q;
  88. }
  89.  
  90. function getShouldSingleColumn() {
  91. if (typeof cachedSCUsage == 'boolean') return cachedSCUsage;
  92. const { clientHeight, clientWidth } = document.documentElement;
  93. if (clientHeight > clientWidth) {
  94. const referenceVideoHeight = clientWidth * videoRatio;
  95. const belowSpace = clientHeight - referenceVideoHeight;
  96. if (belowSpace > -1e-3 && belowSpace - ENABLE_WHEN_CONTENT_OCCUPY_MORE_THAN * clientHeight > -1e-3 && belowSpace > 65) {
  97. return (cachedSCUsage = true);
  98. }
  99. }
  100. return (cachedSCUsage = false);
  101. }
  102.  
  103. /** @type {Set<WeakRef<Object>>} */
  104. const querySet = new Set();
  105. const protoFnQueryChanged = async () => {
  106.  
  107. await customElements.whenDefined('iron-media-query');
  108. const dummy = document.querySelector('iron-media-query') || document.createElement('iron-media-query');
  109. const cProto = getProto(dummy);
  110.  
  111. if (typeof cProto.queryChanged !== 'function') return;
  112. if (cProto.queryChanged71) return;
  113. if (cProto.queryChanged.length !== 0) return;
  114. cProto.queryChanged71 = cProto.queryChanged;
  115.  
  116. cProto.queryChanged = function () {
  117.  
  118. /** @type {string} */
  119. let q = this.query;
  120.  
  121. if (q) {
  122.  
  123. if (!this.addedToSet53_) {
  124. this.addedToSet53_ = 1;
  125. querySet.add(new WeakRef(this));
  126. }
  127.  
  128. if (!bypass) {
  129. if (q.length > 3 && !q.includes('.')) {
  130. this.lastQuery53_ = q;
  131. }
  132. }
  133.  
  134.  
  135. if (this.lastQuery53_) {
  136.  
  137. if (_isSingleColumnPreferred) {
  138. q = toQueryForcedOneCol(this.lastQuery53_);
  139. } else if (_forceTwoCols) {
  140. q = toQueryForcedTwoCols(this.lastQuery53_);
  141. } else {
  142. q = this.lastQuery53_;
  143. }
  144.  
  145. }
  146.  
  147. if (q !== this.query && typeof q === 'string' && q) {
  148. this.query = q;
  149. }
  150.  
  151. }
  152.  
  153. return this.queryChanged71();
  154.  
  155. }
  156.  
  157. };
  158.  
  159. const createCSSElement = ()=>{
  160. const cssElm = document.createElement('style');
  161. cssElm.id = 'oh7T7lsvcHJQ';
  162. document.head.appendChild(cssElm);
  163.  
  164. cssElm.textContent = `
  165.  
  166. ytd-watch-flexy[flexy][is-two-columns_] {
  167. --ytd-watch-flexy-min-player-height-ss: 10px;
  168. }
  169. ytd-watch-flexy[flexy][is-two-columns_] #primary.ytd-watch-flexy {
  170. min-width: calc(var(--ytd-watch-flexy-min-player-height-ss)*1.7777777778);
  171. }
  172. ytd-watch-flexy[flexy][is-two-columns_]:not([is-four-three-to-sixteen-nine-video_]):not([is-extra-wide-video_]):not([full-bleed-player][full-bleed-no-max-width-columns]):not([fixed-panels]) #primary.ytd-watch-flexy {
  173. min-width: calc(var(--ytd-watch-flexy-min-player-height-ss)*1.7777777778);
  174. }
  175. `;
  176. return cssElm;
  177. }
  178.  
  179. const protoFnRatioChanged = async () => {
  180.  
  181. await customElements.whenDefined('ytd-watch-flexy');
  182. const dummy = document.querySelector('ytd-watch-flexy') || document.createElement('ytd-watch-flexy');
  183. const cProto = getProto(dummy);
  184.  
  185. if (typeof cProto.videoHeightToWidthRatioChanged_ !== 'function') return;
  186. if (cProto.videoHeightToWidthRatioChanged23_) return;
  187. // if (cProto.videoHeightToWidthRatioChanged_.length !== 2) return;
  188.  
  189. cProto.videoHeightToWidthRatioChanged23_ = cProto.videoHeightToWidthRatioChanged_;
  190. const ratioQueryFix24_ = () => {
  191.  
  192. if (videoRatio > 1e-5) { } else return;
  193. let changeCSS = false;
  194.  
  195. const changedSingleColumn = _isSingleColumnPreferred !== (_isSingleColumnPreferred = getShouldSingleColumn());
  196. let action = 0;
  197. if (changedSingleColumn) {
  198. action |= 4;
  199. }
  200. if (!_isSingleColumnPreferred) {
  201. const isVerticalRatio = videoRatio > 1.6 && videoRatio < 2.7;
  202. if (isVerticalRatio && !_forceTwoCols) {
  203. changeCSS = true;
  204. _forceTwoCols = 1;
  205. action |= 1;
  206. } else if (!isVerticalRatio && _forceTwoCols) {
  207. changeCSS = true;
  208. _forceTwoCols = 0;
  209. action |= 2;
  210. }
  211. }
  212. if (action) {
  213. for (const p of querySet) {
  214. const qnt = p.deref();
  215. if (!qnt || !qnt.lastQuery53_) continue;
  216. if (action & 4) {
  217. if (!qnt.q00 && !qnt.q02 && _isSingleColumnPreferred) {
  218. qnt.q00 = qnt.lastQuery53_;
  219. qnt.q02 = toQueryForcedOneCol(qnt.q00);
  220. }
  221. action |= 8;
  222. }
  223. if (action & 1) {
  224. if (!qnt.q00 && !qnt.q01) {
  225. qnt.q00 = qnt.lastQuery53_;
  226. qnt.q01 = toQueryForcedTwoCols(qnt.q00);
  227. }
  228. if (qnt.q00 && qnt.q01) {
  229. action |= 8;
  230. }
  231. } else if (action & 2) {
  232. if (qnt.q00 && qnt.q01) {
  233. action |= 8;
  234. }
  235. }
  236. }
  237.  
  238. if (action & 8) {
  239. bypass = true;
  240. for (const p of querySet) {
  241. const qnt = p.deref();
  242. if (qnt && qnt.lastQuery53_ && qnt.query) {
  243. qnt.queryChanged();
  244. }
  245. }
  246. bypass = false;
  247. }
  248.  
  249. }
  250.  
  251. let cssElm = null;
  252.  
  253. if (changeCSS) {
  254. cssElm = cssElm || document.querySelector('style#oh7T7lsvcHJQ') || createCSSElement();
  255. } else {
  256. cssElm = cssElm || document.querySelector('style#oh7T7lsvcHJQ');
  257. }
  258. if (cssElm) {
  259. if (_forceTwoCols && cssElm.disabled) cssElm.disabled = false;
  260. else if (!_forceTwoCols && !cssElm.disabled) cssElm.disabled = true;
  261. }
  262. };
  263.  
  264. const resizePipeline = createPipeline();
  265.  
  266. cProto.videoHeightToWidthRatioChanged_ = function () {
  267. try {
  268. cachedSCUsage = null;
  269. videoRatio = this.videoHeightToWidthRatio_;
  270. resizePipeline(ratioQueryFix24_);
  271. } catch (e) {
  272. }
  273. return this.videoHeightToWidthRatioChanged23_(...arguments);
  274. };
  275.  
  276. let rzid = 0;
  277. Window.prototype.addEventListener.call(window, 'resize', function () {
  278. cachedSCUsage = null;
  279. if (videoRatio > 1e-5) { } else return;
  280. if (rzid > 1e9) rzid = 9;
  281. const t = ++rzid;
  282. resizePipeline(async () => {
  283. if (t !== rzid) return;
  284. await getRafPromise();
  285. if (t !== rzid) return;
  286. let k = getShouldSingleColumn();
  287. if (_isSingleColumnPreferred !== k) {
  288. resizePipeline(ratioQueryFix24_);
  289. }
  290. });
  291. }, { capture: false, passive: true });
  292.  
  293. };
  294.  
  295. window._ytConfigHacks.add((config_) => {
  296.  
  297. const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS;
  298.  
  299. if (EXPERIMENT_FLAGS) {
  300.  
  301. EXPERIMENT_FLAGS.kevlar_set_internal_player_size = false; // vertical live -> schedulePlayerSizeUpdate_
  302.  
  303. }
  304.  
  305. });
  306.  
  307. (async () => {
  308.  
  309. if (!document.documentElement) await ytZara.docInitializedAsync(); // wait for document.documentElement is provided
  310.  
  311. await ytZara.promiseRegistryReady(); // wait for YouTube's customElement Registry is provided (old browser only)
  312.  
  313. protoFnQueryChanged();
  314. protoFnRatioChanged();
  315.  
  316. })();
  317.  
  318. })();

QingJ © 2025

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