YouTube Music: Audio Only

No Video Streaming

目前为 2024-04-06 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Music: Audio Only
  3. // @description No Video Streaming
  4. // @namespace UserScript
  5. // @version 0.1.10
  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' && entry.serializedExperimentFlags.length > 16) {
  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. function fixThumbnailURL(src) {
  380. if (typeof src === 'string' && src.length >= 4) {
  381. let m = /\b[a-z0-9]{4,13}\.jpg\b/.exec(src);
  382. if (m && m[0]) {
  383. const t = m[0];
  384. let idx = src.indexOf(t);
  385. let nSrc = idx >= 0 ? src.substring(0, idx + t.length) : '';
  386. return nSrc;
  387. }
  388. }
  389. return src;
  390. }
  391.  
  392. const avFix = async () => {
  393.  
  394. // if (cw < 6) cw = 6;
  395.  
  396. // const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0;
  397. // ytConfigFix(config_);
  398.  
  399.  
  400. const songImageThumbnail = document.querySelector('#song-image #thumbnail');
  401. if (songImageThumbnail) {
  402.  
  403. if (songImageThumbnail.getAttribute('object-fit') !== 'CONTAIN') songImageThumbnail.setAttribute('object-fit', 'CONTAIN');
  404.  
  405. mo2.observe(songImageThumbnail, { attributes: true });
  406.  
  407. const img = HTMLElement.prototype.querySelector.call(songImageThumbnail, 'img#img[src]');
  408. if (img) {
  409.  
  410. mo2.observe(img, { attributes: true });
  411.  
  412. const src = img.getAttribute('src');
  413.  
  414. let nSrc = fixThumbnailURL(src);
  415. if (nSrc !== src && nSrc && src) {
  416. // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg?sqp=-oa&rs=A
  417. // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg
  418. img.setAttribute('src', nSrc)
  419. }
  420.  
  421. /*
  422. iurl: "default.jpg",
  423. iurlmq: "mqdefault.jpg",
  424. iurlhq: "hqdefault.jpg",
  425. iurlsd: "sddefault.jpg",
  426. iurlpop1: "pop1.jpg",
  427. iurlpop2: "pop2.jpg",
  428. iurlhq720: "hq720.jpg",
  429. iurlmaxres: "maxresdefault.jpg"
  430. */
  431.  
  432. }
  433.  
  434. }
  435.  
  436. for (const s of document.querySelectorAll('[playback-mode][selected-item-has-video]')) {
  437. s.removeAttribute('selected-item-has-video');
  438. }
  439.  
  440. for (const s of document.querySelectorAll('ytmusic-player-page')) {
  441. // s.setAttribute('has-av-switcher', '')
  442. s.removeAttribute('has-av-switcher')
  443. }
  444.  
  445. for (const s of document.querySelectorAll('[video-mode]')) {
  446. s.removeAttribute('video-mode')
  447. }
  448.  
  449. for (const ytElement of document.querySelectorAll('ytmusic-player-page')) {
  450. if (ytElement.is === 'ytmusic-player-page') {
  451. mo2.observe(ytElement, { attributes: true });
  452.  
  453. const cnt = insp(ytElement);
  454.  
  455. const cProto = cnt.constructor.prototype;
  456.  
  457. if (!cProto.setFn322) {
  458. cProto.setFn322 = function () {
  459. if (this.videoMode === true) this.videoMode = false;
  460. // if (this.hasAvSwitcher === false) this.hasAvSwitcher = true;
  461. if (this.hasAvSwitcher === true) this.hasAvSwitcher = false;
  462. }
  463. }
  464.  
  465. if (typeof cProto.computeShowAvSwitcher === 'function' && !cProto.computeShowAvSwitcher322) {
  466. cProto.computeShowAvSwitcher322 = cProto.computeShowAvSwitcher;
  467. cProto.computeShowAvSwitcher = function () {
  468. this.setFn322();
  469. return this.computeShowAvSwitcher322(...arguments);
  470. }
  471. }
  472.  
  473.  
  474. cnt.setFn322();
  475. }
  476. }
  477.  
  478.  
  479. for (const ytElement of document.querySelectorAll('ytmusic-av-toggle')) {
  480. if (ytElement.is === 'ytmusic-av-toggle') {
  481. mo2.observe(ytElement, { attributes: true });
  482.  
  483. const cnt = insp(ytElement);
  484. // cnt.toggleDisabled = false;
  485. const cProto = cnt.constructor.prototype;
  486.  
  487. if (!cProto.setFn322) {
  488. cProto.setFn322 = function () {
  489. if (this.mustPlayAudioOnly === false) this.mustPlayAudioOnly = true;
  490. // if(this.isVideo === true) this.isVideo = false;
  491. // if(this.playbackMode !== 'ATV_PREFERRED') this.playbackMode = 'ATV_PREFERRED';
  492. if (this.selectedItemHasVideo === true) this.selectedItemHasVideo = false;
  493. }
  494. }
  495.  
  496. if (typeof cProto.computeToggleDisabled === 'function' && !cProto.computeToggleDisabled322) {
  497. cProto.computeToggleDisabled322 = cProto.computeToggleDisabled;
  498. cProto.computeToggleDisabled = function () {
  499. this.setFn322();
  500. return this.computeToggleDisabled322(...arguments);
  501. }
  502. }
  503.  
  504.  
  505. cnt.setFn322();
  506. // cnt.computeToggleDisabled = ()=>{};
  507. // if(cnt.isVideo === true) cnt.isVideo = false;
  508. // cnt.mustPlayAudioOnly = false;
  509. // cnt.playbackMode = 'ATV_PREFERRED';
  510. // if(cnt.selectedItemHasVideo === true) cnt.selectedItemHasVideo = false;
  511.  
  512. if (!cnt.onVideoAvToggleTap322 && typeof cnt.onVideoAvToggleTap === 'function' && cnt.onVideoAvToggleTap.length === 0) {
  513. cnt.onVideoAvToggleTap322 = cnt.onVideoAvToggleTap;
  514. cnt.onVideoAvToggleTap = function () {
  515.  
  516. const pr = new Proxy(this, {
  517. get(target, prop) {
  518. // if (prop === 'mustPlayAudioOnly') return true;
  519. // if (prop === 'playbackMode') return 'NONE';
  520. if (prop === 'selectedItemHasVideo') return false;
  521. // if (prop === 'isVideo') return false;
  522. let v = target[prop];
  523. // if (typeof v === 'function') return () => { };
  524. return v;
  525. },
  526. set(target, prop, value) {
  527. return true;
  528. }
  529. });
  530.  
  531. this.onVideoAvToggleTap322.call(pr);
  532.  
  533. }
  534. }
  535.  
  536.  
  537. if (!cnt.onSongAvToggleTap322 && typeof cnt.onSongAvToggleTap === 'function' && cnt.onSongAvToggleTap.length === 0) {
  538. cnt.onSongAvToggleTap322 = cnt.onSongAvToggleTap;
  539. cnt.onSongAvToggleTap = function () {
  540.  
  541. const pr = new Proxy(this, {
  542. get(target, prop) {
  543. // if (prop === 'mustPlayAudioOnly') return true;
  544. // if (prop === 'playbackMode') return 'NONE';
  545. if (prop === 'selectedItemHasVideo') return false;
  546. // if (prop === 'isVideo') return false;
  547. let v = target[prop];
  548. // if (typeof v === 'function') return () => { };
  549. return v;
  550. },
  551. set(target, prop, value) {
  552. return true;
  553. }
  554. });
  555.  
  556. this.onSongAvToggleTap322.call(pr);
  557.  
  558. }
  559. }
  560. cnt.onSongAvToggleTap();
  561. // cnt.playbackMode = 'ATV_PREFERRED';
  562.  
  563. }
  564. }
  565.  
  566.  
  567.  
  568. }
  569.  
  570. const mo = new MutationObserver(() => {
  571. if (cw > 0) {
  572. cw--;
  573. avFix();
  574. }
  575. });
  576.  
  577. mo.observe(document, { childList: true, subtree: true });
  578.  
  579.  
  580. const mo2 = new MutationObserver(() => {
  581. if (cw < 1) cw = 1;
  582. if (cw > 0) {
  583. cw--;
  584. avFix();
  585. }
  586. });
  587.  
  588.  
  589.  
  590.  
  591. document.addEventListener('fullscreenchange', () => {
  592. if (cw < 3) cw = 3;
  593. });
  594.  
  595. document.addEventListener('yt-navigate-start', () => {
  596. if (cw < 3) cw = 3;
  597. });
  598.  
  599. document.addEventListener('yt-navigate-finish', () => {
  600. if (cw < 6) cw = 6;
  601. avFix();
  602. });
  603.  
  604. document.addEventListener('yt-navigate-cache', () => {
  605. if (cw < 3) cw = 3;
  606. });
  607.  
  608.  
  609. window.addEventListener("updateui", function () {
  610. if (cw < 3) cw = 3;
  611. });
  612.  
  613. window.addEventListener("resize", () => {
  614. if (cw < 3) cw = 3;
  615. });
  616. window.addEventListener("state-navigatestart", function () {
  617. if (cw < 3) cw = 3;
  618. });
  619. window.addEventListener("state-navigateend", () => {
  620. if (cw < 6) cw = 6;
  621. avFix();
  622. })
  623.  
  624.  
  625. let cv = null;
  626. document.addEventListener('durationchange', (evt) => {
  627. const target = (evt || 0).target;
  628. if (!(target instanceof HTMLMediaElement)) return;
  629. const targetClassList = target.classList || 0;
  630. const isPlayerVideo = typeof targetClassList.contains === 'function' ? targetClassList.contains('video-stream') && targetClassList.contains('html5-main-video') : false;
  631.  
  632. if (isPlayerVideo) {
  633.  
  634. if (target.readyState === 1 && target.networkState === 2) {
  635. target.__spfgs__ = true;
  636. if (cv) {
  637. cv.resolve();
  638. cv = null;
  639. }
  640. } else {
  641. target.__spfgs__ = false;
  642.  
  643. }
  644.  
  645. if (cw < 6) cw = 6;
  646. avFix();
  647.  
  648.  
  649. }
  650. }, true);
  651.  
  652.  
  653.  
  654. (() => {
  655.  
  656. XMLHttpRequest = (() => {
  657. const XMLHttpRequest_ = XMLHttpRequest;
  658. if ('__xmMc8__' in XMLHttpRequest_.prototype) return XMLHttpRequest_;
  659. const url0 = createObjectURL(new Blob([], { type: 'text/plain' }));
  660. const c = class XMLHttpRequest extends XMLHttpRequest_ {
  661. constructor(...args) {
  662. super(...args);
  663. }
  664. open(method, url, ...args) {
  665. let skip = false;
  666. if (!url || typeof url !== 'string') skip = true;
  667. else if (typeof url === 'string') {
  668. let turl = url[0] === '/' ? `.youtube.com${url}` : `${url}`;
  669. if (turl.includes('googleads') || turl.includes('doubleclick.net')) {
  670. skip = true;
  671. } else if (turl.includes('.youtube.com/pagead/')) {
  672. skip = true;
  673. } else if (turl.includes('.youtube.com/ptracking')) {
  674. skip = true;
  675. } else if (turl.includes('.youtube.com/api/stats/')) { // /api/stats/
  676. // skip = true; // for user activity logging e.g. watched videos
  677. } else if (turl.includes('play.google.com/log')) {
  678. skip = true;
  679. } else if (turl.includes('.youtube.com//?')) { // //?cpn=
  680. skip = true;
  681. }
  682. }
  683. if (!skip) {
  684. this.__xmMc8__ = 1;
  685. return super.open(method, url, ...args);
  686. } else {
  687. this.__xmMc8__ = 2;
  688. return super.open('GET', url0);
  689. }
  690. }
  691. send(...args) {
  692. if (this.__xmMc8__ === 1) {
  693. return super.send(...args);
  694. } else if (this.__xmMc8__ === 2) {
  695. return super.send();
  696. } else {
  697. console.log('xhr warning');
  698. return super.send(...args);
  699. }
  700. }
  701. }
  702. c.prototype.__xmMc8__ = 0;
  703. return c;
  704. })();
  705.  
  706. const s7 = Symbol();
  707. const f7 = () => true;
  708.  
  709. !window.canRetry9048 && generalRegister('canRetry', s7, (p) => {
  710. 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'
  711. }, {
  712. get() {
  713. if ('logger' in this && 'policy' in this && 'xhr' && this) {
  714. 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"
  715. // OKAY !
  716. console.log('canRetry05 - ', this.errorMessage)
  717. return f7;
  718. }
  719. // console.log(this)
  720. console.log('canRetry02 - ', this.errorMessage, this)
  721. } else {
  722. console.log('canRetry ERR - ', this.errorMessage)
  723. }
  724. return this[s7];
  725. },
  726. set(nv) {
  727. this[s7] = nv;
  728. return true;
  729. },
  730. enumerable: false,
  731. configurable: true
  732. });
  733. window.canRetry9048 = 1;
  734.  
  735. })();
  736.  
  737. attachOneTimeEvent('yt-action', function () {
  738. const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0;
  739. ytConfigFix(config_);
  740. });
  741.  
  742. let prepared = false;
  743. function prepare() {
  744. if (prepared) return;
  745. prepared = true;
  746.  
  747. if (typeof _yt_player !== 'undefined' && _yt_player && typeof _yt_player === 'object') {
  748.  
  749. for (const [k, v] of Object.entries(_yt_player)) {
  750.  
  751. const p = typeof v === 'function' ? v.prototype : 0;
  752.  
  753. if (p
  754. && typeof p.clone === 'function'
  755. && typeof p.get === 'function' && typeof p.set === 'function'
  756. && typeof p.isEmpty === 'undefined' && typeof p.forEach === 'undefined'
  757. && typeof p.clear === 'undefined'
  758. ) {
  759.  
  760. key = k;
  761.  
  762. }
  763.  
  764. }
  765.  
  766. }
  767.  
  768. if (key) {
  769.  
  770. const ClassX = _yt_player[key];
  771. _yt_player[key] = class extends ClassX {
  772. constructor(...args) {
  773.  
  774. if (typeof args[0] === 'string' && args[0].startsWith('http://')) args[0] = '';
  775. super(...args);
  776.  
  777. }
  778. }
  779. _yt_player[key].luX1Y = 1;
  780. }
  781.  
  782. }
  783. let s3 = Symbol();
  784.  
  785. generalRegister('deviceIsAudioOnly', s3, (p) => {
  786. return typeof p.getPlayerType === 'function' && typeof p.getVideoEmbedCode === 'function' && typeof p.getVideoUrl === 'function' && !p.onCueRangeEnter && !p.getVideoData && !('ATTRIBUTE_NODE' in p)
  787. }, {
  788.  
  789. get() {
  790. return this[s3];
  791. },
  792. set(nv) {
  793. if (typeof nv === 'boolean') this[s3] = true;
  794. else this[s3] = undefined;
  795. prepare();
  796. return true;
  797. },
  798. enumerable: false,
  799. configurable: true
  800.  
  801. });
  802.  
  803.  
  804. let s1 = Symbol();
  805. let s2 = Symbol();
  806. Object.defineProperty(Object.prototype, 'defraggedFromSubfragments', {
  807. get() {
  808. // console.log(501, this.constructor.prototype)
  809. return undefined;
  810. },
  811. set(nv) {
  812. return true;
  813. },
  814. enumerable: false,
  815. configurable: true
  816. });
  817.  
  818. Object.defineProperty(Object.prototype, 'hasSubfragmentedFmp4', {
  819. get() {
  820. // console.log(502, this.constructor.prototype)
  821. return this[s1];
  822. },
  823. set(nv) {
  824. if (typeof nv === 'boolean') this[s1] = false;
  825. else this[s1] = undefined;
  826. return true;
  827. },
  828. enumerable: false,
  829. configurable: true
  830. });
  831.  
  832. Object.defineProperty(Object.prototype, 'hasSubfragmentedWebm', {
  833. get() {
  834. // console.log(503, this.constructor.prototype)
  835. return this[s2];
  836. },
  837. set(nv) {
  838. if (typeof nv === 'boolean') this[s2] = false;
  839. else this[s2] = undefined;
  840. return true;
  841. },
  842. enumerable: false,
  843. configurable: true
  844. });
  845.  
  846.  
  847. const supportedFormatsConfig = () => {
  848.  
  849. function typeTest(type) {
  850. if (typeof type === 'string' && type.startsWith('video/')) {
  851. return false;
  852. }
  853. }
  854.  
  855. // return a custom MIME type checker that can defer to the original function
  856. function makeModifiedTypeChecker(origChecker) {
  857. // Check if a video type is allowed
  858. return function (type) {
  859. let res = undefined;
  860. if (type === undefined) res = false;
  861. else {
  862. res = typeTest.call(this, type);
  863. }
  864. if (res === undefined) res = origChecker.apply(this, arguments);
  865. return res;
  866. };
  867. }
  868.  
  869. // Override video element canPlayType() function
  870. const proto = (HTMLVideoElement || 0).prototype;
  871. if (proto && typeof proto.canPlayType == 'function') {
  872. proto.canPlayType = makeModifiedTypeChecker(proto.canPlayType);
  873. }
  874.  
  875. // Override media source extension isTypeSupported() function
  876. const mse = window.MediaSource;
  877. // Check for MSE support before use
  878. if (mse && typeof mse.isTypeSupported == 'function') {
  879. mse.isTypeSupported = makeModifiedTypeChecker(mse.isTypeSupported);
  880. }
  881.  
  882. };
  883.  
  884. supportedFormatsConfig();
  885. }
  886.  
  887. const isEnable = (typeof GM !== 'undefined' && typeof GM.getValue === 'function') ? (await GM.getValue("isEnable_aWsjF", true)) : null;
  888. if (typeof isEnable !== 'boolean') throw new DOMException("Please Update your browser", "NotSupportedError");
  889. if (isEnable) {
  890. const element = document.createElement('button');
  891. element.setAttribute('onclick', `(${pageInjectionCode})()`);
  892. element.click();
  893. }
  894.  
  895. GM_registerMenuCommand(`Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`, async function () {
  896. await GM.setValue("isEnable_aWsjF", !isEnable);
  897. location.reload();
  898. });
  899.  
  900. let messageCount = 0;
  901. let busy = false;
  902. window.addEventListener('message', (evt) => {
  903.  
  904. const v = ((evt || 0).data || 0).ZECxh;
  905. if (typeof v === 'boolean') {
  906. if (messageCount > 1e9) messageCount = 9;
  907. const t = ++messageCount;
  908. if (v && isEnable) {
  909. requestAnimationFrame(async () => {
  910. if (t !== messageCount) return;
  911. if (busy) return;
  912. busy = true;
  913. if (await confirm("Livestream is detected. Press OK to disable YouTube Audio Mode.")) {
  914. await GM.setValue("isEnable_aWsjF", !isEnable);
  915. location.reload();
  916. }
  917. busy = false;
  918. });
  919. }
  920. }
  921.  
  922. });
  923.  
  924.  
  925. const pLoad = new Promise(resolve => {
  926. if (document.readyState !== 'loading') {
  927. resolve();
  928. } else {
  929. window.addEventListener("DOMContentLoaded", resolve, false);
  930. }
  931. });
  932.  
  933.  
  934. function contextmenuInfoItemAppearedFn(target) {
  935.  
  936. const btn = target.closest('[role="option"]');
  937. if (!btn) return;
  938. if (btn.parentNode.querySelector('[role="option"].audio-only-toggle-btn')) return;
  939. document.documentElement.classList.add('with-audio-only-toggle-btn');
  940. const newBtn = btn.cloneNode(true);
  941. const h = () => {
  942. newBtn.classList.remove('iron-selected');
  943. newBtn.classList.remove('focused');
  944. newBtn.removeAttribute('iron-selected');
  945. newBtn.removeAttribute('focused');
  946. let a = newBtn.querySelector('a');
  947. if (a) a.removeAttribute('href');
  948. newBtn.classList.add('audio-only-toggle-btn');
  949. }
  950. h();
  951. async function reloadPage() {
  952. await GM.setValue("isEnable_aWsjF", !isEnable);
  953. document.documentElement.setAttribute('forceRefresh032', '');
  954. location.reload();
  955. }
  956. newBtn.addEventListener('click', (evt) => {
  957. evt.preventDefault();
  958. evt.stopImmediatePropagation();
  959. evt.stopPropagation();
  960. reloadPage();
  961. }, true);
  962. btn.parentNode.insertBefore(newBtn, null);
  963. // let t;
  964. // let h = 0;
  965. // t = btn.closest('.ytp-panel-menu[style*="height"]');
  966. // if (t) t.style.height = t.scrollHeight + 'px';
  967. // t = btn.closest('.ytp-panel[style*="height"]');
  968. // if (t) t.style.height = (h = t.scrollHeight) + 'px';
  969. // t = btn.closest('.ytp-popup.ytp-contextmenu[style*="height"]');
  970. // if (t && h > 0) t.style.height = h + 'px';
  971.  
  972. const f = () => {
  973. h();
  974. const mx = newBtn.querySelector('yt-formatted-string');
  975. if (mx) {
  976. mx.removeAttribute('is-empty');
  977. mx.textContent = `Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`;
  978. }
  979. let t;
  980. t = btn.closest('ytmusic-menu-popup-renderer[style*="max-height"]');
  981. if (t) t.style.maxHeight = t.scrollHeight + 'px';
  982. }
  983. f();
  984. setTimeout(f, 40);
  985.  
  986.  
  987. }
  988.  
  989.  
  990. function mobileMenuItemAppearedFn(target) {
  991.  
  992. }
  993.  
  994.  
  995. pLoad.then(() => {
  996.  
  997. document.addEventListener('animationstart', (evt) => {
  998. const animationName = evt.animationName;
  999. if (!animationName) return;
  1000.  
  1001. if (animationName === 'contextmenuInfoItemAppeared') contextmenuInfoItemAppearedFn(evt.target);
  1002. if (animationName === 'mobileMenuItemAppeared') mobileMenuItemAppearedFn(evt.target);
  1003.  
  1004. }, true);
  1005.  
  1006.  
  1007. const style = document.createElement('style');
  1008. style.id = 'fm9v0';
  1009. style.textContent = `
  1010.  
  1011. .html5-video-player {
  1012. background-color: black;
  1013. }
  1014.  
  1015. #song-image.ytmusic-player {
  1016. background-color: black;
  1017. }
  1018.  
  1019. ytmusic-player-page:not([player-fullscreened]) #main-panel.style-scope.ytmusic-player-page[style*="padding"] {
  1020. padding: 0px 0px !important;
  1021. box-sizing: border-box;
  1022. }
  1023.  
  1024. ytmusic-player-page:not([player-fullscreened]) ytmusic-player#player.style-scope.ytmusic-player-page {
  1025. max-height: 100%;
  1026. margin-top: calc(-1*var(--ytmusic-player-page-vertical-padding));
  1027. box-sizing: border-box;
  1028. }
  1029.  
  1030. /* #movie_player > .ytp-iv-video-content {
  1031. pointer-events: none; // allow clicking
  1032. } */
  1033.  
  1034. #movie_player > .html5-video-container:not(:empty) {
  1035. box-sizing: border-box;
  1036. height: 100%;
  1037. }
  1038.  
  1039. @keyframes mobileMenuItemAppeared {
  1040. 0% {
  1041. background-position-x: 3px;
  1042. }
  1043. 100% {
  1044. background-position-x: 4px;
  1045. }
  1046. }
  1047. ytm-select.player-speed-settings ~ ytm-menu-item:last-of-type {
  1048. animation: mobileMenuItemAppeared 1ms linear 0s 1 normal forwards;
  1049. }
  1050. @keyframes contextmenuInfoItemAppeared {
  1051. 0% {
  1052. background-position-x: 3px;
  1053. }
  1054. 100% {
  1055. background-position-x: 4px;
  1056. }
  1057. }
  1058. ytmusic-popup-container.ytmusic-app ytmusic-menu-popup-renderer tp-yt-paper-listbox > [role="option"]:first-child {
  1059. animation: contextmenuInfoItemAppeared 1ms linear 0s 1 normal forwards;
  1060. }
  1061. #confirmDialog794 {
  1062. z-index:999999 !important;
  1063. display: none;
  1064. /* Hidden by default */
  1065. position: fixed;
  1066. /* Stay in place */
  1067. z-index: 1;
  1068. /* Sit on top */
  1069. left: 0;
  1070. top: 0;
  1071. width: 100%;
  1072. /* Full width */
  1073. height: 100%;
  1074. /* Full height */
  1075. overflow: auto;
  1076. /* Enable scroll if needed */
  1077. background-color: rgba(0,0,0,0.4);
  1078. /* Black w/ opacity */
  1079. }
  1080. #confirmDialog794 .confirm-box {
  1081. position:relative;
  1082. color: black;
  1083. z-index:999999 !important;
  1084. background-color: #fefefe;
  1085. margin: 15% auto;
  1086. /* 15% from the top and centered */
  1087. padding: 20px;
  1088. border: 1px solid #888;
  1089. width: 30%;
  1090. /* Could be more or less, depending on screen size */
  1091. box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  1092. }
  1093. #confirmDialog794 .confirm-buttons {
  1094. text-align: right;
  1095. }
  1096. #confirmDialog794 button {
  1097. margin-left: 10px;
  1098. }
  1099. `
  1100. document.head.appendChild(style);
  1101. })
  1102.  
  1103.  
  1104. })();

QingJ © 2025

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