Greasy Fork镜像 支持简体中文。

YouTube Music: Audio Only

No Video Streaming

目前為 2024-02-03 提交的版本,檢視 最新版本

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

QingJ © 2025

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