YouTube Music: Audio Only

No Video Streaming

  1. // ==UserScript==
  2. // @name YouTube Music: Audio Only
  3. // @description No Video Streaming
  4. // @description:en No Video Streaming
  5. // @description:ja No Video Streaming
  6. // @description:zh-TW No Video Streaming
  7. // @description:zh-CN No Video Streaming
  8. // @namespace UserScript
  9. // @version 0.1.20
  10. // @author CY Fung
  11. // @match https://music.youtube.com/*
  12. // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
  13. // @icon https://raw.githubusercontent.com/cyfung1031/userscript-supports/main/icons/YouTube-Audio-Only.png
  14. // @grant GM_registerMenuCommand
  15. // @grant GM.setValue
  16. // @grant GM.getValue
  17. // @run-at document-start
  18. // @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@5d83d154956057bdde19e24f95b332cb9a78fcda/library/default-trusted-type-policy.js
  19. // @license MIT
  20. // @compatible chrome
  21. // @compatible firefox
  22. // @compatible opera
  23. // @compatible edge
  24. // @compatible safari
  25. // @allFrames true
  26. //
  27. // ==/UserScript==
  28.  
  29. (async function () {
  30. 'use strict';
  31.  
  32.  
  33.  
  34. const defaultPolicy = (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy) || { createHTML: s => s };
  35. function createHTML(s) {
  36. return defaultPolicy.createHTML(s);
  37. }
  38.  
  39. let trustHTMLErr = null;
  40. try {
  41. document.createElement('div').innerHTML = createHTML('1');
  42. } catch (e) {
  43. trustHTMLErr = e;
  44. }
  45.  
  46. if (trustHTMLErr) {
  47. console.log(`trustHTMLErr`, trustHTMLErr);
  48. trustHTMLErr(); // exit userscript
  49. }
  50.  
  51. /** @type {globalThis.PromiseConstructor} */
  52. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  53.  
  54. if (typeof AbortSignal === 'undefined') throw new DOMException("Please update your browser.", "NotSupportedError");
  55.  
  56. async function confirm(message) {
  57. // Create the HTML for the dialog
  58.  
  59. if (!document.body) return;
  60.  
  61. let dialog = document.getElementById('confirmDialog794');
  62. if (!dialog) {
  63.  
  64. const dialogHTML = `
  65. <div id="confirmDialog794" class="dialog-style" style="display: block;">
  66. <div class="confirm-box">
  67. <p>${message}</p>
  68. <div class="confirm-buttons">
  69. <button id="confirmBtn">Confirm</button>
  70. <button id="cancelBtn">Cancel</button>
  71. </div>
  72. </div>
  73. </div>
  74. `;
  75.  
  76. // Append the dialog to the document body
  77. document.body.insertAdjacentHTML('beforeend', createHTML(dialogHTML));
  78. dialog = document.getElementById('confirmDialog794');
  79.  
  80. }
  81.  
  82. // Return a promise that resolves or rejects based on the user's choice
  83. return new Promise((resolve) => {
  84. document.getElementById('confirmBtn').onclick = () => {
  85. resolve(true);
  86. cleanup();
  87. };
  88.  
  89. document.getElementById('cancelBtn').onclick = () => {
  90. resolve(false);
  91. cleanup();
  92. };
  93.  
  94. function cleanup() {
  95. dialog && dialog.remove();
  96. dialog = null;
  97. }
  98. });
  99. }
  100.  
  101.  
  102.  
  103. if (location.pathname === '/live_chat' || location.pathname === 'live_chat_replay') return;
  104.  
  105. const kEventListener = (evt) => {
  106. if (document.documentElement.hasAttribute('forceRefresh032')) {
  107. evt.stopImmediatePropagation();
  108. evt.stopPropagation();
  109. }
  110. }
  111. window.addEventListener('beforeunload', kEventListener, false);
  112.  
  113. const pageInjectionCode = function () {
  114.  
  115. const A_D_B_Y_PASS = true;
  116.  
  117. if (typeof AbortSignal === 'undefined') throw new DOMException("Please update your browser.", "NotSupportedError");
  118.  
  119. const URL = window.URL || new Function('return URL')();
  120. const createObjectURL = URL.createObjectURL.bind(URL);
  121.  
  122. /** @type {globalThis.PromiseConstructor} */
  123. const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
  124.  
  125. const PromiseExternal = ((resolve_, reject_) => {
  126. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  127. return class PromiseExternal extends Promise {
  128. constructor(cb = h) {
  129. super(cb);
  130. if (cb === h) {
  131. /** @type {(value: any) => void} */
  132. this.resolve = resolve_;
  133. /** @type {(reason?: any) => void} */
  134. this.reject = reject_;
  135. }
  136. }
  137. };
  138. })();
  139.  
  140.  
  141.  
  142.  
  143.  
  144. const createPipeline = () => {
  145. let pipelineMutex = Promise.resolve();
  146. const pipelineExecution = fn => {
  147. return new Promise((resolve, reject) => {
  148. pipelineMutex = pipelineMutex.then(async () => {
  149. let res;
  150. try {
  151. res = await fn();
  152. } catch (e) {
  153. console.log(e);
  154. reject(e);
  155. }
  156. resolve(res);
  157. }).catch(console.warn);
  158. });
  159. };
  160. return pipelineExecution;
  161. }
  162.  
  163. const observablePromise = (proc, timeoutPromise) => {
  164. let promise = null;
  165. return {
  166. obtain() {
  167. if (!promise) {
  168. promise = new Promise(resolve => {
  169. let mo = null;
  170. const f = () => {
  171. let t = proc();
  172. if (t) {
  173. mo.disconnect();
  174. mo.takeRecords();
  175. mo = null;
  176. resolve(t);
  177. }
  178. }
  179. mo = new MutationObserver(f);
  180. mo.observe(document, { subtree: true, childList: true })
  181. f();
  182. timeoutPromise && timeoutPromise.then(() => {
  183. resolve(null)
  184. });
  185. });
  186. }
  187. return promise
  188. }
  189. }
  190. }
  191.  
  192.  
  193. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  194.  
  195. const prototypeInherit = (d, b) => {
  196. const m = Object.getOwnPropertyDescriptors(b);
  197. for (const p in m) {
  198. if (!Object.getOwnPropertyDescriptor(d, p)) {
  199. Object.defineProperty(d, p, m[p]);
  200. }
  201. }
  202. };
  203.  
  204. let setTimeout_ = setTimeout;
  205. let clearTimeout_ = clearTimeout;
  206.  
  207. const delayPn = delay => new Promise((fn => setTimeout_(fn, delay)));
  208.  
  209.  
  210. const mockEvent = (o, elem) => {
  211. o = o || {};
  212. elem = elem || null;
  213. return {
  214. preventDefault: () => { },
  215. stopPropagation: () => { },
  216. stopImmediatePropagation: () => { },
  217. returnValue: true,
  218. target: elem,
  219. srcElement: elem,
  220. defaultPrevented: false,
  221. cancelable: true,
  222. timeStamp: performance.now(),
  223. ...o
  224. }
  225. };
  226.  
  227.  
  228. const generalRegister = (prop, symbol, checker, pg) => {
  229. const objSet = new Set();
  230. let done = false;
  231. const f = (o) => {
  232. const ct = o.constructor;
  233. const proto = ct.prototype;
  234. if (!done && proto && ct !== Function && ct !== Object && checker(proto)) {
  235. done = true;
  236. delete Object.prototype[prop];
  237. objSet.delete(proto);
  238. objSet.delete(o);
  239. for (const obj of objSet) {
  240. obj[prop] = obj[symbol];
  241. delete obj[symbol];
  242. }
  243. objSet.clear();
  244. Object.defineProperty(proto, prop, pg);
  245. return proto;
  246. }
  247. return false;
  248. };
  249. Object.defineProperty(Object.prototype, prop, {
  250. get() {
  251. const p = f(this);
  252. if (p) {
  253. return p[prop];
  254. } else {
  255. return this[symbol];
  256. }
  257. },
  258. set(nv) {
  259. const p = f(this);
  260. if (p) {
  261. p[prop] = nv;
  262. } else {
  263. objSet.add(this);
  264. this[symbol] = nv;
  265. }
  266. return true;
  267. },
  268. enumerable: false,
  269. configurable: true
  270. });
  271.  
  272. };
  273.  
  274. if (!Object.defineProperty322 && typeof Object.defineProperty === 'function' && Object.defineProperty.length === 3) {
  275. // _definePropertyAccessor
  276. Object.defineProperty322 = Object.defineProperty;
  277. const st = new Set(
  278. [
  279. 'videoMode', 'hasAvSwitcher', 'isVideo',
  280. 'playbackMode', 'selectedItemHasVideo'
  281. ]
  282. );
  283. const defineProperty322 = Object.defineProperty322;
  284. if (defineProperty322) {
  285.  
  286. Object.defineProperty = function (o, k, t) {
  287. if (typeof o.is === 'string') {
  288. if (!('configurable' in t) && typeof t.get === 'function' && typeof t.set === 'function') {
  289. t.configurable = true;
  290. if (st.has(k)) {
  291. t.set = function (e) {
  292. this._setPendingProperty(k, e, !0) && this._invalidateProperties()
  293. }
  294. }
  295. }
  296. }
  297. return defineProperty322(o, k, t);
  298. }
  299. }
  300. }
  301.  
  302. const updateLastActiveTimeAsync = (player_) => {
  303. // TBC
  304. Promise.resolve().then(() => {
  305. if (typeof player_.updateLastActiveTime === 'function') {
  306. player_.updateLastActiveTime();
  307. }
  308. });
  309. };
  310.  
  311. const attachOneTimeEvent = function (eventType, callback) {
  312. let kz = false;
  313. document.addEventListener(eventType, function (evt) {
  314. if (kz) return;
  315. kz = true;
  316. callback(evt);
  317. }, { capture: true, passive: true, once: true });
  318. }
  319.  
  320. function removeTempObjectProp01() {
  321. delete Object.prototype['kevlar_non_watch_unified_player'];
  322. delete Object.prototype['kevlar_unified_player'];
  323. }
  324.  
  325. function ytConfigFix(config__) {
  326. const config_ = config__;
  327.  
  328. if (config_) {
  329.  
  330. const playerKevlar = ((config_ || 0).WEB_PLAYER_CONTEXT_CONFIGS || 0).WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH || 0;
  331.  
  332. if (playerKevlar) {
  333.  
  334. // console.log(322, playerKevlar)
  335. playerKevlar.allowWoffleManagement = false;
  336. playerKevlar.cinematicSettingsAvailable = false;
  337. playerKevlar.showMiniplayerButton = false;
  338. playerKevlar.showMiniplayerUiWhenMinimized = false;
  339. playerKevlar.transparentBackground = false;
  340.  
  341. playerKevlar.enableCsiLogging = false;
  342. playerKevlar.externalFullscreen = false;
  343.  
  344. if (typeof playerKevlar.serializedExperimentFlags === 'string') {
  345. playerKevlar.serializedExperimentFlags = '';
  346. // playerKevlar.serializedExperimentFlags = playerKevlar.serializedExperimentFlags.replace(/[-\w]+=(\[\]|[.-\d]+|[_a-z]+|)(&|$)/g,'').replace(/&$/,'')
  347. }
  348.  
  349. if (typeof playerKevlar.serializedExperimentIds === 'string') {
  350. playerKevlar.serializedExperimentIds = '';
  351. // playerKevlar.serializedExperimentIds = playerKevlar.serializedExperimentIds.replace(/\d+\s*(,\s*|$)/g,'')
  352. }
  353.  
  354. }
  355.  
  356. removeTempObjectProp01();
  357.  
  358. let configs = config_.WEB_PLAYER_CONTEXT_CONFIGS || {};
  359. for (const [key, entry] of Object.entries(configs)) {
  360.  
  361. if (entry && typeof entry.serializedExperimentFlags === 'string' && entry.serializedExperimentFlags.length > 16) {
  362. // prevent idle playback failure
  363. entry.serializedExperimentFlags = entry.serializedExperimentFlags.replace(/\b(html5_check_for_idle_network_interval_ms|html5_trigger_loader_when_idle_network|html5_sabr_fetch_on_idle_network_preloaded_players|html5_autonav_cap_idle_secs|html5_autonav_quality_cap|html5_disable_client_autonav_cap_for_onesie|html5_idle_rate_limit_ms|html5_sabr_fetch_on_idle_network_preloaded_players|html5_webpo_idle_priority_job|html5_server_playback_start_policy|html5_check_video_data_errors_before_playback_start|html5_check_unstarted|html5_check_queue_on_data_loaded)=([-_\w]+)(\&|$)/g, (_, a, b, c) => {
  364. return a + '00' + '=' + b + c;
  365. });
  366.  
  367. }
  368.  
  369. }
  370.  
  371. const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS;
  372.  
  373. if (EXPERIMENT_FLAGS) {
  374. EXPERIMENT_FLAGS.kevlar_unified_player = true;
  375. EXPERIMENT_FLAGS.kevlar_non_watch_unified_player = true;
  376. }
  377.  
  378.  
  379. const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS;
  380.  
  381. if (EXPERIMENTS_FORCED_FLAGS) {
  382. EXPERIMENTS_FORCED_FLAGS.kevlar_unified_player = true;
  383. EXPERIMENTS_FORCED_FLAGS.kevlar_non_watch_unified_player = true;
  384. }
  385.  
  386. }
  387. }
  388.  
  389. Object.defineProperty(Object.prototype, 'kevlar_non_watch_unified_player', {
  390. get() {
  391. // console.log(501, this.constructor.prototype)
  392. return true;
  393. },
  394. set(nv) {
  395. return true;
  396. },
  397. enumerable: false,
  398. configurable: true
  399. });
  400.  
  401.  
  402. Object.defineProperty(Object.prototype, 'kevlar_unified_player', {
  403. get() {
  404. // console.log(501, this.constructor.prototype)
  405. return true;
  406. },
  407. set(nv) {
  408. return true;
  409. },
  410. enumerable: false,
  411. configurable: true
  412. });
  413.  
  414.  
  415.  
  416. let cw = 0;
  417. function fixThumbnailURL(src) {
  418. if (typeof src === 'string' && src.length >= 4) {
  419. let m = /\b[a-z0-9]{4,13}\.jpg\b/.exec(src);
  420. if (m && m[0]) {
  421. const t = m[0];
  422. let idx = src.indexOf(t);
  423. let nSrc = idx >= 0 ? src.substring(0, idx + t.length) : '';
  424. return nSrc;
  425. }
  426. }
  427. return src;
  428. }
  429.  
  430. const avFix = async () => {
  431.  
  432. // if (cw < 6) cw = 6;
  433.  
  434. // const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0;
  435. // ytConfigFix(config_);
  436.  
  437.  
  438. const songImageThumbnail = document.querySelector('#song-image #thumbnail');
  439. if (songImageThumbnail) {
  440.  
  441. if (songImageThumbnail.getAttribute('object-fit') !== 'CONTAIN') songImageThumbnail.setAttribute('object-fit', 'CONTAIN');
  442.  
  443. mo2.observe(songImageThumbnail, { attributes: true });
  444.  
  445. const img = HTMLElement.prototype.querySelector.call(songImageThumbnail, 'img#img[src]');
  446. if (img) {
  447.  
  448. mo2.observe(img, { attributes: true });
  449.  
  450. const src = img.getAttribute('src');
  451.  
  452. let nSrc = fixThumbnailURL(src);
  453. if (nSrc !== src && nSrc && src) {
  454. // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg?sqp=-oa&rs=A
  455. // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg
  456. img.setAttribute('src', nSrc)
  457. }
  458.  
  459. /*
  460. iurl: "default.jpg",
  461. iurlmq: "mqdefault.jpg",
  462. iurlhq: "hqdefault.jpg",
  463. iurlsd: "sddefault.jpg",
  464. iurlpop1: "pop1.jpg",
  465. iurlpop2: "pop2.jpg",
  466. iurlhq720: "hq720.jpg",
  467. iurlmaxres: "maxresdefault.jpg"
  468. */
  469.  
  470. }
  471.  
  472. }
  473.  
  474. for (const s of document.querySelectorAll('[playback-mode][selected-item-has-video]')) {
  475. s.removeAttribute('selected-item-has-video');
  476. }
  477.  
  478. for (const s of document.querySelectorAll('ytmusic-player-page')) {
  479. // s.setAttribute('has-av-switcher', '')
  480. s.removeAttribute('has-av-switcher')
  481. }
  482.  
  483. for (const s of document.querySelectorAll('[video-mode]')) {
  484. s.removeAttribute('video-mode')
  485. }
  486.  
  487. for (const ytElement of document.querySelectorAll('ytmusic-player-page')) {
  488. if (ytElement.is === 'ytmusic-player-page') {
  489. mo2.observe(ytElement, { attributes: true });
  490.  
  491. const cnt = insp(ytElement);
  492.  
  493. const cProto = cnt.constructor.prototype;
  494.  
  495. if (!cProto.setFn322) {
  496. cProto.setFn322 = function () {
  497. if (this.videoMode === true) this.videoMode = false;
  498. // if (this.hasAvSwitcher === false) this.hasAvSwitcher = true;
  499. if (this.hasAvSwitcher === true) this.hasAvSwitcher = false;
  500. }
  501. }
  502.  
  503. if (typeof cProto.computeShowAvSwitcher === 'function' && !cProto.computeShowAvSwitcher322) {
  504. cProto.computeShowAvSwitcher322 = cProto.computeShowAvSwitcher;
  505. cProto.computeShowAvSwitcher = function () {
  506. this.setFn322();
  507. return this.computeShowAvSwitcher322(...arguments);
  508. }
  509. }
  510.  
  511.  
  512. cnt.setFn322();
  513. }
  514. }
  515.  
  516.  
  517. for (const ytElement of document.querySelectorAll('ytmusic-av-toggle')) {
  518. if (ytElement.is === 'ytmusic-av-toggle') {
  519. mo2.observe(ytElement, { attributes: true });
  520.  
  521. const cnt = insp(ytElement);
  522. // cnt.toggleDisabled = false;
  523. const cProto = cnt.constructor.prototype;
  524.  
  525. if (!cProto.setFn322) {
  526. cProto.setFn322 = function () {
  527. if (this.mustPlayAudioOnly === false) this.mustPlayAudioOnly = true;
  528. // if(this.isVideo === true) this.isVideo = false;
  529. // if(this.playbackMode !== 'ATV_PREFERRED') this.playbackMode = 'ATV_PREFERRED';
  530. if (this.selectedItemHasVideo === true) this.selectedItemHasVideo = false;
  531. }
  532. }
  533.  
  534. if (typeof cProto.computeToggleDisabled === 'function' && !cProto.computeToggleDisabled322) {
  535. cProto.computeToggleDisabled322 = cProto.computeToggleDisabled;
  536. cProto.computeToggleDisabled = function () {
  537. this.setFn322();
  538. return this.computeToggleDisabled322(...arguments);
  539. }
  540. }
  541.  
  542.  
  543. cnt.setFn322();
  544. // cnt.computeToggleDisabled = ()=>{};
  545. // if(cnt.isVideo === true) cnt.isVideo = false;
  546. // cnt.mustPlayAudioOnly = false;
  547. // cnt.playbackMode = 'ATV_PREFERRED';
  548. // if(cnt.selectedItemHasVideo === true) cnt.selectedItemHasVideo = false;
  549.  
  550. if (!cnt.onVideoAvToggleTap322 && typeof cnt.onVideoAvToggleTap === 'function' && cnt.onVideoAvToggleTap.length === 0) {
  551. cnt.onVideoAvToggleTap322 = cnt.onVideoAvToggleTap;
  552. cnt.onVideoAvToggleTap = function () {
  553.  
  554. const pr = new Proxy(this, {
  555. get(target, prop) {
  556. // if (prop === 'mustPlayAudioOnly') return true;
  557. // if (prop === 'playbackMode') return 'NONE';
  558. if (prop === 'selectedItemHasVideo') return false;
  559. // if (prop === 'isVideo') return false;
  560. let v = target[prop];
  561. // if (typeof v === 'function') return () => { };
  562. return v;
  563. },
  564. set(target, prop, value) {
  565. return true;
  566. }
  567. });
  568.  
  569. this.onVideoAvToggleTap322.call(pr);
  570.  
  571. }
  572. }
  573.  
  574.  
  575. if (!cnt.onSongAvToggleTap322 && typeof cnt.onSongAvToggleTap === 'function' && cnt.onSongAvToggleTap.length === 0) {
  576. cnt.onSongAvToggleTap322 = cnt.onSongAvToggleTap;
  577. cnt.onSongAvToggleTap = function () {
  578.  
  579. const pr = new Proxy(this, {
  580. get(target, prop) {
  581. // if (prop === 'mustPlayAudioOnly') return true;
  582. // if (prop === 'playbackMode') return 'NONE';
  583. if (prop === 'selectedItemHasVideo') return false;
  584. // if (prop === 'isVideo') return false;
  585. let v = target[prop];
  586. // if (typeof v === 'function') return () => { };
  587. return v;
  588. },
  589. set(target, prop, value) {
  590. return true;
  591. }
  592. });
  593.  
  594. this.onSongAvToggleTap322.call(pr);
  595.  
  596. }
  597. }
  598. cnt.onSongAvToggleTap();
  599. // cnt.playbackMode = 'ATV_PREFERRED';
  600.  
  601. }
  602. }
  603.  
  604. if (A_D_B_Y_PASS) Promise.resolve().then(() => {
  605. // skip a$d.s
  606.  
  607. const isAdsPlaying = document.querySelector('[is-advertisement-playing]');
  608. if (isAdsPlaying) {
  609. const audios = document.querySelectorAll('audio');
  610. const onlyAudio = audios.length === 1 ? audios[0] : 0;
  611. if (onlyAudio instanceof HTMLMediaElement && onlyAudio.paused === false && onlyAudio.currentTime > 0 && onlyAudio.duration > onlyAudio.currentTime && onlyAudio.playbackRate < 1.2 && onlyAudio.playbackRate > 0.8){
  612. try {
  613. if (onlyAudio.duration - onlyAudio.currentTime > 0.7) onlyAudio.currentTime += onlyAudio.duration - onlyAudio.currentTime - 0.52 - 0.14 * Math.random();
  614. } catch (e) { }
  615. onlyAudio.playbackRate = 15 - Math.random() * 0.04;
  616. }
  617. }
  618.  
  619. }).catch(console.warn);
  620.  
  621. }
  622.  
  623. const mo = new MutationObserver(() => {
  624. if (cw > 0) {
  625. cw--;
  626. avFix();
  627. }
  628. });
  629.  
  630. mo.observe(document, { childList: true, subtree: true });
  631.  
  632.  
  633. const mo2 = new MutationObserver(() => {
  634. if (cw < 1) cw = 1;
  635. if (cw > 0) {
  636. cw--;
  637. avFix();
  638. }
  639. });
  640.  
  641.  
  642.  
  643.  
  644. document.addEventListener('fullscreenchange', () => {
  645. if (cw < 3) cw = 3;
  646. });
  647.  
  648. document.addEventListener('yt-navigate-start', () => {
  649. if (cw < 3) cw = 3;
  650. });
  651.  
  652. document.addEventListener('yt-navigate-finish', () => {
  653. if (cw < 6) cw = 6;
  654. avFix();
  655. });
  656.  
  657. document.addEventListener('yt-navigate-cache', () => {
  658. if (cw < 3) cw = 3;
  659. });
  660.  
  661.  
  662. window.addEventListener("updateui", function () {
  663. if (cw < 3) cw = 3;
  664. });
  665.  
  666. window.addEventListener("resize", () => {
  667. if (cw < 3) cw = 3;
  668. });
  669. window.addEventListener("state-navigatestart", function () {
  670. if (cw < 3) cw = 3;
  671. });
  672. window.addEventListener("state-navigateend", () => {
  673. if (cw < 6) cw = 6;
  674. avFix();
  675. })
  676.  
  677.  
  678. let cv = null;
  679. document.addEventListener('durationchange', (evt) => {
  680. const target = (evt || 0).target;
  681. if (!(target instanceof HTMLMediaElement)) return;
  682. const targetClassList = target.classList || 0;
  683. const isPlayerVideo = typeof targetClassList.contains === 'function' ? targetClassList.contains('video-stream') && targetClassList.contains('html5-main-video') : false;
  684.  
  685. if (isPlayerVideo) {
  686.  
  687. if (target.readyState === 1 && target.networkState === 2) {
  688. target.__spfgs__ = true;
  689. if (cv) {
  690. cv.resolve();
  691. cv = null;
  692. }
  693. } else {
  694. target.__spfgs__ = false;
  695.  
  696. }
  697.  
  698. if (cw < 6) cw = 6;
  699. avFix();
  700.  
  701.  
  702. }
  703. }, true);
  704.  
  705.  
  706.  
  707. (() => {
  708.  
  709. XMLHttpRequest = (() => {
  710. const XMLHttpRequest_ = XMLHttpRequest;
  711. if ('__xmMc8__' in XMLHttpRequest_.prototype) return XMLHttpRequest_;
  712. const url0 = createObjectURL(new Blob([], { type: 'text/plain' }));
  713. const c = class XMLHttpRequest extends XMLHttpRequest_ {
  714. constructor(...args) {
  715. super(...args);
  716. }
  717. open(method, url, ...args) {
  718. let skip = false;
  719. if (!url || typeof url !== 'string') skip = true;
  720. else if (typeof url === 'string') {
  721. let turl = url[0] === '/' ? `.youtube.com${url}` : `${url}`;
  722. if (turl.includes('googleads') || turl.includes('doubleclick.net')) {
  723. skip = true;
  724. } else if (turl.includes('.youtube.com/pagead/')) {
  725. skip = true;
  726. } else if (turl.includes('.youtube.com/ptracking')) {
  727. skip = true;
  728. } else if (turl.includes('.youtube.com/api/stats/')) { // /api/stats/
  729. // skip = true; // for user activity logging e.g. watched videos
  730. } else if (turl.includes('play.google.com/log')) {
  731. skip = true;
  732. } else if (turl.includes('.youtube.com//?')) { // //?cpn=
  733. skip = true;
  734. }
  735. }
  736. if (!skip) {
  737. this.__xmMc8__ = 1;
  738. return super.open(method, url, ...args);
  739. } else {
  740. this.__xmMc8__ = 2;
  741. return super.open('GET', url0);
  742. }
  743. }
  744. send(...args) {
  745. if (this.__xmMc8__ === 1) {
  746. return super.send(...args);
  747. } else if (this.__xmMc8__ === 2) {
  748. return super.send();
  749. } else {
  750. console.log('xhr warning');
  751. return super.send(...args);
  752. }
  753. }
  754. }
  755. c.prototype.__xmMc8__ = 0;
  756. return c;
  757. })();
  758.  
  759. const s7 = Symbol();
  760. const f7 = () => true;
  761.  
  762. !window.canRetry9048 && generalRegister('canRetry', s7, (p) => {
  763. return typeof p.onStateChange === 'function' && typeof p.dispose === 'function' && typeof p.hide === 'undefined' && typeof p.show === 'undefined' && typeof p.isComplete === 'undefined' && typeof p.getDuration === 'undefined'
  764. }, {
  765. get() {
  766. if ('logger' in this && 'policy' in this && 'xhr' && this) {
  767. if (this.errorMessage && typeof this.errorMessage === 'string' && this.errorMessage.includes('XMLHttpRequest') && this.errorMessage.includes('Invalid URL')) { // "SyntaxError_Failed to execute 'open' on 'XMLHttpRequest': Invalid URL"
  768. // OKAY !
  769. console.log('canRetry05 - ', this.errorMessage)
  770. return f7;
  771. }
  772. // console.log(this)
  773. console.log('canRetry02 - ', this.errorMessage, this)
  774. } else {
  775. console.log('canRetry ERR - ', this.errorMessage)
  776. }
  777. return this[s7];
  778. },
  779. set(nv) {
  780. this[s7] = nv;
  781. return true;
  782. },
  783. enumerable: false,
  784. configurable: true
  785. });
  786. window.canRetry9048 = 1;
  787.  
  788. })();
  789.  
  790. attachOneTimeEvent('yt-action', function () {
  791. const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0;
  792. ytConfigFix(config_);
  793. });
  794.  
  795. let prepared = false;
  796. function prepare() {
  797. if (prepared) return;
  798. prepared = true;
  799.  
  800. if (typeof _yt_player !== 'undefined' && _yt_player && typeof _yt_player === 'object') {
  801.  
  802. for (const [k, v] of Object.entries(_yt_player)) {
  803.  
  804. const p = typeof v === 'function' ? v.prototype : 0;
  805.  
  806. if (p
  807. && typeof p.clone === 'function'
  808. && typeof p.get === 'function' && typeof p.set === 'function'
  809. && typeof p.isEmpty === 'undefined' && typeof p.forEach === 'undefined'
  810. && typeof p.clear === 'undefined'
  811. ) {
  812.  
  813. key = k;
  814.  
  815. }
  816.  
  817. }
  818.  
  819. }
  820.  
  821. if (key) {
  822.  
  823. const ClassX = _yt_player[key];
  824. _yt_player[key] = class extends ClassX {
  825. constructor(...args) {
  826.  
  827. if (typeof args[0] === 'string' && args[0].startsWith('http://')) args[0] = '';
  828. super(...args);
  829.  
  830. }
  831. }
  832. _yt_player[key].luX1Y = 1;
  833. prototypeInherit(_yt_player[key].prototype, ClassX.prototype);
  834. }
  835.  
  836. }
  837. let s3 = Symbol();
  838.  
  839. generalRegister('deviceIsAudioOnly', s3, (p) => {
  840. return typeof p.getPlayerType === 'function' && typeof p.getVideoEmbedCode === 'function' && typeof p.getVideoUrl === 'function' && !p.onCueRangeEnter && !p.getVideoData && !('ATTRIBUTE_NODE' in p)
  841. }, {
  842.  
  843. get() {
  844. return this[s3];
  845. },
  846. set(nv) {
  847. if (typeof nv === 'boolean') this[s3] = true;
  848. else this[s3] = undefined;
  849. prepare();
  850. return true;
  851. },
  852. enumerable: false,
  853. configurable: true
  854.  
  855. });
  856.  
  857.  
  858. let s1 = Symbol();
  859. let s2 = Symbol();
  860. Object.defineProperty(Object.prototype, 'defraggedFromSubfragments', {
  861. get() {
  862. // console.log(501, this.constructor.prototype)
  863. return undefined;
  864. },
  865. set(nv) {
  866. return true;
  867. },
  868. enumerable: false,
  869. configurable: true
  870. });
  871.  
  872. Object.defineProperty(Object.prototype, 'hasSubfragmentedFmp4', {
  873. get() {
  874. // console.log(502, this.constructor.prototype)
  875. return this[s1];
  876. },
  877. set(nv) {
  878. if (typeof nv === 'boolean') this[s1] = false;
  879. else this[s1] = undefined;
  880. return true;
  881. },
  882. enumerable: false,
  883. configurable: true
  884. });
  885.  
  886. Object.defineProperty(Object.prototype, 'hasSubfragmentedWebm', {
  887. get() {
  888. // console.log(503, this.constructor.prototype)
  889. return this[s2];
  890. },
  891. set(nv) {
  892. if (typeof nv === 'boolean') this[s2] = false;
  893. else this[s2] = undefined;
  894. return true;
  895. },
  896. enumerable: false,
  897. configurable: true
  898. });
  899.  
  900.  
  901. const supportedFormatsConfig = () => {
  902.  
  903. function typeTest(type) {
  904. if (typeof type === 'string' && type.startsWith('video/')) {
  905. return false;
  906. }
  907. }
  908.  
  909. // return a custom MIME type checker that can defer to the original function
  910. function makeModifiedTypeChecker(origChecker) {
  911. // Check if a video type is allowed
  912. return function (type) {
  913. let res = undefined;
  914. if (type === undefined) res = false;
  915. else {
  916. res = typeTest.call(this, type);
  917. }
  918. if (res === undefined) res = origChecker.apply(this, arguments);
  919. return res;
  920. };
  921. }
  922.  
  923. // Override video element canPlayType() function
  924. const proto = (HTMLVideoElement || 0).prototype;
  925. if (proto && typeof proto.canPlayType == 'function') {
  926. proto.canPlayType = makeModifiedTypeChecker(proto.canPlayType);
  927. }
  928.  
  929. // Override media source extension isTypeSupported() function
  930. const mse = window.MediaSource;
  931. // Check for MSE support before use
  932. if (mse && typeof mse.isTypeSupported == 'function') {
  933. mse.isTypeSupported = makeModifiedTypeChecker(mse.isTypeSupported);
  934. }
  935.  
  936. };
  937.  
  938. // supportedFormatsConfig(); // avoid issue due to failure on only video source (like ads)
  939. }
  940.  
  941. const isEnable = (typeof GM !== 'undefined' && typeof GM.getValue === 'function') ? (await GM.getValue("isEnable_aWsjF", true)) : null;
  942. if (typeof isEnable !== 'boolean') throw new DOMException("Please Update your browser", "NotSupportedError");
  943. if (isEnable) {
  944. const element = document.createElement('button');
  945. element.setAttribute('onclick', createHTML(`(${pageInjectionCode})()`));
  946. element.click();
  947. }
  948.  
  949. GM_registerMenuCommand(`Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`, async function () {
  950. await GM.setValue("isEnable_aWsjF", !isEnable);
  951. location.reload();
  952. });
  953.  
  954. let messageCount = 0;
  955. let busy = false;
  956. window.addEventListener('message', (evt) => {
  957.  
  958. const v = ((evt || 0).data || 0).ZECxh;
  959. if (typeof v === 'boolean') {
  960. if (messageCount > 1e9) messageCount = 9;
  961. const t = ++messageCount;
  962. if (v && isEnable) {
  963. requestAnimationFrame(async () => {
  964. if (t !== messageCount) return;
  965. if (busy) return;
  966. busy = true;
  967. if (await confirm("Livestream is detected. Press OK to disable YouTube Audio Mode.")) {
  968. await GM.setValue("isEnable_aWsjF", !isEnable);
  969. location.reload();
  970. }
  971. busy = false;
  972. });
  973. }
  974. }
  975.  
  976. });
  977.  
  978.  
  979. const pLoad = new Promise(resolve => {
  980. if (document.readyState !== 'loading') {
  981. resolve();
  982. } else {
  983. window.addEventListener("DOMContentLoaded", resolve, false);
  984. }
  985. });
  986.  
  987.  
  988. function contextmenuInfoItemAppearedFn(target) {
  989.  
  990. const btn = target.closest('[role="option"]');
  991. if (!btn) return;
  992. if (btn.parentNode.querySelector('[role="option"].audio-only-toggle-btn')) return;
  993. document.documentElement.classList.add('with-audio-only-toggle-btn');
  994. const newBtn = btn.cloneNode(true);
  995. const h = () => {
  996. newBtn.classList.remove('iron-selected');
  997. newBtn.classList.remove('focused');
  998. newBtn.removeAttribute('iron-selected');
  999. newBtn.removeAttribute('focused');
  1000. let a = newBtn.querySelector('a');
  1001. if (a) a.removeAttribute('href');
  1002. newBtn.classList.add('audio-only-toggle-btn');
  1003. }
  1004. h();
  1005. async function reloadPage() {
  1006. await GM.setValue("isEnable_aWsjF", !isEnable);
  1007. document.documentElement.setAttribute('forceRefresh032', '');
  1008. location.reload();
  1009. }
  1010. newBtn.addEventListener('click', (evt) => {
  1011. evt.preventDefault();
  1012. evt.stopImmediatePropagation();
  1013. evt.stopPropagation();
  1014. reloadPage();
  1015. }, true);
  1016. btn.parentNode.insertBefore(newBtn, null);
  1017. // let t;
  1018. // let h = 0;
  1019. // t = btn.closest('.ytp-panel-menu[style*="height"]');
  1020. // if (t) t.style.height = t.scrollHeight + 'px';
  1021. // t = btn.closest('.ytp-panel[style*="height"]');
  1022. // if (t) t.style.height = (h = t.scrollHeight) + 'px';
  1023. // t = btn.closest('.ytp-popup.ytp-contextmenu[style*="height"]');
  1024. // if (t && h > 0) t.style.height = h + 'px';
  1025.  
  1026. const f = () => {
  1027. h();
  1028. const mx = newBtn.querySelector('yt-formatted-string');
  1029. if (mx) {
  1030. mx.removeAttribute('is-empty');
  1031. mx.textContent = `Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`;
  1032. }
  1033. let t;
  1034. t = btn.closest('ytmusic-menu-popup-renderer[style*="max-height"]');
  1035. if (t) t.style.maxHeight = t.scrollHeight + 'px';
  1036. }
  1037. f();
  1038. setTimeout(f, 40);
  1039.  
  1040.  
  1041. }
  1042.  
  1043.  
  1044. function mobileMenuItemAppearedFn(target) {
  1045.  
  1046. }
  1047.  
  1048.  
  1049. pLoad.then(() => {
  1050.  
  1051. document.addEventListener('animationstart', (evt) => {
  1052. const animationName = evt.animationName;
  1053. if (!animationName) return;
  1054.  
  1055. if (animationName === 'contextmenuInfoItemAppeared') contextmenuInfoItemAppearedFn(evt.target);
  1056. if (animationName === 'mobileMenuItemAppeared') mobileMenuItemAppearedFn(evt.target);
  1057.  
  1058. }, true);
  1059.  
  1060.  
  1061. const style = document.createElement('style');
  1062. style.id = 'fm9v0';
  1063. style.textContent = `
  1064.  
  1065. .html5-video-player {
  1066. background-color: black;
  1067. }
  1068.  
  1069. #song-image.ytmusic-player {
  1070. background-color: black;
  1071. }
  1072.  
  1073. ytmusic-player-page:not([player-fullscreened]) #main-panel.style-scope.ytmusic-player-page[style*="padding"] {
  1074. padding: 0px 0px !important;
  1075. box-sizing: border-box;
  1076. }
  1077.  
  1078. ytmusic-player-page:not([player-fullscreened]) ytmusic-player#player.style-scope.ytmusic-player-page {
  1079. max-height: 100%;
  1080. margin-top: calc(-1*var(--ytmusic-player-page-vertical-padding));
  1081. box-sizing: border-box;
  1082. }
  1083.  
  1084. /* #movie_player > .ytp-iv-video-content {
  1085. pointer-events: none; // allow clicking
  1086. } */
  1087.  
  1088. #movie_player > .html5-video-container:not(:empty) {
  1089. box-sizing: border-box;
  1090. height: 100%;
  1091. }
  1092.  
  1093. @keyframes mobileMenuItemAppeared {
  1094. 0% {
  1095. background-position-x: 3px;
  1096. }
  1097. 100% {
  1098. background-position-x: 4px;
  1099. }
  1100. }
  1101. ytm-select.player-speed-settings ~ ytm-menu-item:last-of-type {
  1102. animation: mobileMenuItemAppeared 1ms linear 0s 1 normal forwards;
  1103. }
  1104. @keyframes contextmenuInfoItemAppeared {
  1105. 0% {
  1106. background-position-x: 3px;
  1107. }
  1108. 100% {
  1109. background-position-x: 4px;
  1110. }
  1111. }
  1112. ytmusic-popup-container.ytmusic-app ytmusic-menu-popup-renderer tp-yt-paper-listbox > [role="option"]:first-child {
  1113. animation: contextmenuInfoItemAppeared 1ms linear 0s 1 normal forwards;
  1114. }
  1115. #confirmDialog794 {
  1116. z-index:999999 !important;
  1117. display: none;
  1118. /* Hidden by default */
  1119. position: fixed;
  1120. /* Stay in place */
  1121. z-index: 1;
  1122. /* Sit on top */
  1123. left: 0;
  1124. top: 0;
  1125. width: 100%;
  1126. /* Full width */
  1127. height: 100%;
  1128. /* Full height */
  1129. overflow: auto;
  1130. /* Enable scroll if needed */
  1131. background-color: rgba(0,0,0,0.4);
  1132. /* Black w/ opacity */
  1133. }
  1134. #confirmDialog794 .confirm-box {
  1135. position:relative;
  1136. color: black;
  1137. z-index:999999 !important;
  1138. background-color: #fefefe;
  1139. margin: 15% auto;
  1140. /* 15% from the top and centered */
  1141. padding: 20px;
  1142. border: 1px solid #888;
  1143. width: 30%;
  1144. /* Could be more or less, depending on screen size */
  1145. box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  1146. }
  1147. #confirmDialog794 .confirm-buttons {
  1148. text-align: right;
  1149. }
  1150. #confirmDialog794 button {
  1151. margin-left: 10px;
  1152. }
  1153. `
  1154. document.head.appendChild(style);
  1155. })
  1156.  
  1157.  
  1158. })();