YouTube: Single Column Tamer

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

目前為 2023-12-08 提交的版本,檢視 最新版本

  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.0.1
  9. // @author CY Fung
  10. // @description Re-adoption of Single Column Detection against video and browser sizes
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (() => {
  15. const ENABLE_WHEN_CONTENT_OCCUPY_MORE_THAN = 0.2 // 20% or more of other content can be displayed in your browser
  16.  
  17. // protait screen & vertical live
  18.  
  19.  
  20. let always_single_column = false;
  21. let bypass = false;
  22.  
  23. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  24.  
  25. const Promise = (async () => { })().constructor;
  26.  
  27. let videoRatio = null;
  28.  
  29. const getProto = (element) => {
  30. if (element) {
  31. const cnt = insp(element);
  32. return cnt.constructor.prototype || null;
  33. }
  34. return null;
  35. }
  36.  
  37. let qm = 0;
  38.  
  39. function changeQ(q) {
  40.  
  41. if (!q || typeof q !== 'string') return q;
  42.  
  43. q = q.replace('1000px', '200.2px');
  44. q = q.replace('629px', '129.2px');
  45. q = q.replace('657px', '157.2px');
  46. q = q.replace('630px', '130.2px');
  47. q = q.replace('1327px', '237.2px');
  48.  
  49. return q;
  50.  
  51. }
  52.  
  53.  
  54. function changeQ_alwaysSingleColumn(q) {
  55.  
  56. if (!q || typeof q !== 'string') return q;
  57.  
  58. q = q.replace('1000px', '998200.3px');
  59. q = q.replace('629px', '998129.3px');
  60. q = q.replace('657px', '998157.3px');
  61. q = q.replace('630px', '998130.3px');
  62. q = q.replace('1327px', '998237.3px');
  63.  
  64. return q;
  65.  
  66. }
  67.  
  68.  
  69. const querySet = new Set();
  70. (async () => {
  71.  
  72. await customElements.whenDefined('iron-media-query');
  73. const dummy = document.querySelector('iron-media-query') || document.createElement('iron-media-query');
  74. const cProto = getProto(dummy);
  75.  
  76. if (typeof cProto.queryChanged !== 'function') return;
  77. if (cProto.queryChanged71) return;
  78. if (cProto.queryChanged.length !== 0) return;
  79. cProto.queryChanged71 = cProto.queryChanged;
  80.  
  81. cProto.queryChanged = function () {
  82.  
  83. /** @type {string} */
  84. let q = this.query;
  85.  
  86. if (q) {
  87.  
  88. if (!this.addedToSet53_) {
  89. this.addedToSet53_ = 1;
  90. querySet.add(new WeakRef(this));
  91. }
  92.  
  93. if (!bypass) {
  94. if (q.length > 3 && !q.includes('.')) {
  95. this.lastQuery53_ = q;
  96. }
  97. }
  98.  
  99.  
  100. if (this.lastQuery53_) {
  101.  
  102. if (always_single_column) {
  103. q = changeQ_alwaysSingleColumn(this.lastQuery53_);
  104. } else if (qm) {
  105. q = changeQ(this.lastQuery53_);
  106. } else {
  107. q = this.lastQuery53_;
  108. }
  109.  
  110. }
  111.  
  112. if (q !== this.query) {
  113. this.query = q;
  114. }
  115.  
  116.  
  117.  
  118.  
  119. }
  120.  
  121. return this.queryChanged71();
  122.  
  123. }
  124.  
  125. })();
  126.  
  127.  
  128. function getShouldSingleColumn() {
  129. const { clientHeight, clientWidth } = document.documentElement;
  130. if (clientHeight > clientWidth) {
  131. let referenceVideoHeight = clientWidth * videoRatio;
  132. let belowSpace = clientHeight - referenceVideoHeight;
  133. if (belowSpace > -1e-3) {
  134. if (belowSpace - ENABLE_WHEN_CONTENT_OCCUPY_MORE_THAN * clientHeight >= -1e-3) {
  135. return true;
  136. }
  137. }
  138. }
  139. return false;
  140. }
  141.  
  142.  
  143. (async () => {
  144.  
  145. await customElements.whenDefined('ytd-watch-flexy');
  146. const dummy = document.querySelector('ytd-watch-flexy') || document.createElement('ytd-watch-flexy');
  147. const cProto = getProto(dummy);
  148.  
  149. cProto.videoHeightToWidthRatioChanged23_ = cProto.videoHeightToWidthRatioChanged_;
  150. const ratioQueryFix24_ = (k) => {
  151.  
  152. if (videoRatio > 1e-5) { } else return;
  153. let changeCSS = false;
  154.  
  155. let always_single_column_ = always_single_column;
  156. always_single_column = typeof k === 'boolean' ? k : getShouldSingleColumn();
  157. let action = 0;
  158. if (always_single_column !== always_single_column_) {
  159. action |= 4;
  160. }
  161. if (!always_single_column) {
  162. if (videoRatio > 1.6 && videoRatio < 2.7) {
  163. if (!qm) {
  164. changeCSS = true;
  165. qm = 1;
  166. action |= 1;
  167. }
  168. } else {
  169. if (qm) {
  170. changeCSS = true;
  171. qm = 0;
  172. action |= 2;
  173. }
  174. }
  175. }
  176. if (action) {
  177. for (const p of querySet) {
  178. let elm = p.deref();
  179. if (!elm || !elm.lastQuery53_) continue;
  180. if (action & 4) {
  181. if (!elm.q00 && !elm.q02 && always_single_column) {
  182. elm.q00 = elm.lastQuery53_;
  183. elm.q02 = changeQ_alwaysSingleColumn(elm.q00);
  184. }
  185. action |= 8;
  186. }
  187. if (action & 1) {
  188. if (!elm.q00 && !elm.q01) {
  189. elm.q00 = elm.lastQuery53_;
  190. elm.q01 = changeQ(elm.q00);
  191. }
  192. if (elm.q00 && elm.q01) {
  193. action |= 8;
  194. }
  195. } else if (action & 2) {
  196. if (elm.q00 && elm.q01) {
  197. action |= 8;
  198. }
  199. }
  200. }
  201.  
  202. if (action & 8) {
  203. bypass = true;
  204. for (const p of querySet) {
  205. let elm = p.deref();
  206. if (!elm) continue;
  207. if (elm.lastQuery53_ && elm.query) {
  208. elm.queryChanged();
  209. }
  210. }
  211. bypass = false;
  212. }
  213.  
  214. }
  215.  
  216. let cssElm = null;
  217.  
  218. if (changeCSS) {
  219.  
  220. cssElm = cssElm | document.querySelector('style#oh7T7lsvcHJQ');
  221.  
  222. if (!cssElm) {
  223.  
  224. cssElm = document.createElement('style');
  225. cssElm.id = 'oh7T7lsvcHJQ';
  226. document.head.appendChild(cssElm);
  227.  
  228. cssElm.textContent = `
  229.  
  230. ytd-watch-flexy[flexy][is-two-columns_] {
  231. --ytd-watch-flexy-min-player-height-ss: 10px;
  232. }
  233. ytd-watch-flexy[flexy][is-two-columns_] #primary.ytd-watch-flexy {
  234. min-width: calc(var(--ytd-watch-flexy-min-player-height-ss)*1.7777777778);
  235. }
  236. 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 {
  237. min-width: calc(var(--ytd-watch-flexy-min-player-height-ss)*1.7777777778);
  238. }
  239. `;
  240.  
  241. }
  242. }
  243.  
  244. cssElm = cssElm | document.querySelector('style#oh7T7lsvcHJQ');
  245. if (cssElm) {
  246. if (qm && cssElm.disabled) cssElm.disabled = false;
  247. else if (!qm && !cssElm.disabled) cssElm.disabled = true;
  248. }
  249. };
  250.  
  251. cProto.videoHeightToWidthRatioChanged_ = function () {
  252. videoRatio = this.videoHeightToWidthRatio_;
  253. Promise.resolve().then(ratioQueryFix24_);
  254. return this.videoHeightToWidthRatioChanged23_.apply(this, arguments);
  255. };
  256.  
  257. let resizeBusy = false;
  258. let resizeQuene = Promise.resolve();
  259.  
  260. Window.prototype.addEventListener.call(window, 'resize', function () {
  261. if (videoRatio > 1e-5) { } else return;
  262. if (resizeBusy) return;
  263. resizeBusy = true;
  264. resizeQuene = resizeQuene.then(() => {
  265. let k = getShouldSingleColumn();
  266. if (always_single_column !== k) {
  267. Promise.resolve(k).then(ratioQueryFix24_);
  268. }
  269. }).then(() => {
  270. resizeBusy = false;
  271. });
  272. }, { capture: false, passive: true });
  273.  
  274. })();
  275.  
  276. })();

QingJ © 2025

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