🏠 Home 

Greasy Fork is available in English.

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