YouTube Music: Audio Only

No Video Streaming

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

  1. // ==UserScript==
  2. // @name YouTube Music: Audio Only
  3. // @description No Video Streaming
  4. // @namespace UserScript
  5. // @version 0.1.8
  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. 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. if (typeof v === 'function' && typeof v.prototype.clone === 'function'
  752. && typeof v.prototype.get === 'function' && typeof v.prototype.set === 'function'
  753.  
  754. && typeof v.prototype.isEmpty === 'undefined' && typeof v.prototype.forEach === 'undefined'
  755. && typeof v.prototype.clear === 'undefined'
  756.  
  757. ) {
  758.  
  759. key = k;
  760.  
  761. }
  762.  
  763. }
  764.  
  765. }
  766.  
  767. if (key) {
  768.  
  769. const ClassX = _yt_player[key];
  770. _yt_player[key] = class extends ClassX {
  771. constructor(...args) {
  772.  
  773. if (typeof args[0] === 'string' && args[0].startsWith('http://')) args[0] = '';
  774. super(...args);
  775.  
  776. }
  777. }
  778. _yt_player[key].luX1Y = 1;
  779. }
  780.  
  781. }
  782. let s3 = Symbol();
  783.  
  784. generalRegister('deviceIsAudioOnly', s3, (p) => {
  785. return typeof p.getPlayerType === 'function' && typeof p.getVideoEmbedCode === 'function' && typeof p.getVideoUrl === 'function' && !p.onCueRangeEnter && !p.getVideoData && !('ATTRIBUTE_NODE' in p)
  786. }, {
  787.  
  788. get() {
  789. return this[s3];
  790. },
  791. set(nv) {
  792. if (typeof nv === 'boolean') this[s3] = true;
  793. else this[s3] = undefined;
  794. prepare();
  795. return true;
  796. },
  797. enumerable: false,
  798. configurable: true
  799.  
  800. });
  801.  
  802.  
  803. let s1 = Symbol();
  804. let s2 = Symbol();
  805. Object.defineProperty(Object.prototype, 'defraggedFromSubfragments', {
  806. get() {
  807. // console.log(501, this.constructor.prototype)
  808. return undefined;
  809. },
  810. set(nv) {
  811. return true;
  812. },
  813. enumerable: false,
  814. configurable: true
  815. });
  816.  
  817. Object.defineProperty(Object.prototype, 'hasSubfragmentedFmp4', {
  818. get() {
  819. // console.log(502, this.constructor.prototype)
  820. return this[s1];
  821. },
  822. set(nv) {
  823. if (typeof nv === 'boolean') this[s1] = false;
  824. else this[s1] = undefined;
  825. return true;
  826. },
  827. enumerable: false,
  828. configurable: true
  829. });
  830.  
  831. Object.defineProperty(Object.prototype, 'hasSubfragmentedWebm', {
  832. get() {
  833. // console.log(503, this.constructor.prototype)
  834. return this[s2];
  835. },
  836. set(nv) {
  837. if (typeof nv === 'boolean') this[s2] = false;
  838. else this[s2] = undefined;
  839. return true;
  840. },
  841. enumerable: false,
  842. configurable: true
  843. });
  844.  
  845.  
  846. const supportedFormatsConfig = () => {
  847.  
  848. function typeTest(type) {
  849. if (typeof type === 'string' && type.startsWith('video/')) {
  850. return false;
  851. }
  852. }
  853.  
  854. // return a custom MIME type checker that can defer to the original function
  855. function makeModifiedTypeChecker(origChecker) {
  856. // Check if a video type is allowed
  857. return function (type) {
  858. let res = undefined;
  859. if (type === undefined) res = false;
  860. else {
  861. res = typeTest.call(this, type);
  862. }
  863. if (res === undefined) res = origChecker.apply(this, arguments);
  864. return res;
  865. };
  866. }
  867.  
  868. // Override video element canPlayType() function
  869. const proto = (HTMLVideoElement || 0).prototype;
  870. if (proto && typeof proto.canPlayType == 'function') {
  871. proto.canPlayType = makeModifiedTypeChecker(proto.canPlayType);
  872. }
  873.  
  874. // Override media source extension isTypeSupported() function
  875. const mse = window.MediaSource;
  876. // Check for MSE support before use
  877. if (mse && typeof mse.isTypeSupported == 'function') {
  878. mse.isTypeSupported = makeModifiedTypeChecker(mse.isTypeSupported);
  879. }
  880.  
  881. };
  882.  
  883. supportedFormatsConfig();
  884. }
  885.  
  886. const isEnable = (typeof GM !== 'undefined' && typeof GM.getValue === 'function') ? (await GM.getValue("isEnable_aWsjF", true)) : null;
  887. if (typeof isEnable !== 'boolean') throw new DOMException("Please Update your browser", "NotSupportedError");
  888. if (isEnable) {
  889. const element = document.createElement('button');
  890. element.setAttribute('onclick', `(${pageInjectionCode})()`);
  891. element.click();
  892. }
  893.  
  894. GM_registerMenuCommand(`Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`, async function () {
  895. await GM.setValue("isEnable_aWsjF", !isEnable);
  896. location.reload();
  897. });
  898.  
  899. let messageCount = 0;
  900. let busy = false;
  901. window.addEventListener('message', (evt) => {
  902.  
  903. const v = ((evt || 0).data || 0).ZECxh;
  904. if (typeof v === 'boolean') {
  905. if (messageCount > 1e9) messageCount = 9;
  906. const t = ++messageCount;
  907. if (v && isEnable) {
  908. requestAnimationFrame(async () => {
  909. if (t !== messageCount) return;
  910. if (busy) return;
  911. busy = true;
  912. if (await confirm("Livestream is detected. Press OK to disable YouTube Audio Mode.")) {
  913. await GM.setValue("isEnable_aWsjF", !isEnable);
  914. location.reload();
  915. }
  916. busy = false;
  917. });
  918. }
  919. }
  920.  
  921. });
  922.  
  923.  
  924. const pLoad = new Promise(resolve => {
  925. if (document.readyState !== 'loading') {
  926. resolve();
  927. } else {
  928. window.addEventListener("DOMContentLoaded", resolve, false);
  929. }
  930. });
  931.  
  932.  
  933. function contextmenuInfoItemAppearedFn(target) {
  934.  
  935. const btn = target.closest('[role="option"]');
  936. if (!btn) return;
  937. if (btn.parentNode.querySelector('[role="option"].audio-only-toggle-btn')) return;
  938. document.documentElement.classList.add('with-audio-only-toggle-btn');
  939. const newBtn = btn.cloneNode(true);
  940. const h = () => {
  941. newBtn.classList.remove('iron-selected');
  942. newBtn.classList.remove('focused');
  943. newBtn.removeAttribute('iron-selected');
  944. newBtn.removeAttribute('focused');
  945. let a = newBtn.querySelector('a');
  946. if (a) a.removeAttribute('href');
  947. newBtn.classList.add('audio-only-toggle-btn');
  948. }
  949. h();
  950. async function reloadPage() {
  951. await GM.setValue("isEnable_aWsjF", !isEnable);
  952. document.documentElement.setAttribute('forceRefresh032', '');
  953. location.reload();
  954. }
  955. newBtn.addEventListener('click', (evt) => {
  956. evt.preventDefault();
  957. evt.stopImmediatePropagation();
  958. evt.stopPropagation();
  959. reloadPage();
  960. }, true);
  961. btn.parentNode.insertBefore(newBtn, null);
  962. // let t;
  963. // let h = 0;
  964. // t = btn.closest('.ytp-panel-menu[style*="height"]');
  965. // if (t) t.style.height = t.scrollHeight + 'px';
  966. // t = btn.closest('.ytp-panel[style*="height"]');
  967. // if (t) t.style.height = (h = t.scrollHeight) + 'px';
  968. // t = btn.closest('.ytp-popup.ytp-contextmenu[style*="height"]');
  969. // if (t && h > 0) t.style.height = h + 'px';
  970.  
  971. const f = () => {
  972. h();
  973. const mx = newBtn.querySelector('yt-formatted-string');
  974. if (mx) {
  975. mx.removeAttribute('is-empty');
  976. mx.textContent = `Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`;
  977. }
  978. let t;
  979. t = btn.closest('ytmusic-menu-popup-renderer[style*="max-height"]');
  980. if (t) t.style.maxHeight = t.scrollHeight + 'px';
  981. }
  982. f();
  983. setTimeout(f, 40);
  984.  
  985.  
  986. }
  987.  
  988.  
  989. function mobileMenuItemAppearedFn(target) {
  990.  
  991. }
  992.  
  993.  
  994. pLoad.then(() => {
  995.  
  996. document.addEventListener('animationstart', (evt) => {
  997. const animationName = evt.animationName;
  998. if (!animationName) return;
  999.  
  1000. if (animationName === 'contextmenuInfoItemAppeared') contextmenuInfoItemAppearedFn(evt.target);
  1001. if (animationName === 'mobileMenuItemAppeared') mobileMenuItemAppearedFn(evt.target);
  1002.  
  1003. }, true);
  1004.  
  1005.  
  1006. const style = document.createElement('style');
  1007. style.id = 'fm9v0';
  1008. style.textContent = `
  1009.  
  1010. .html5-video-player {
  1011. background-color: black;
  1012. }
  1013.  
  1014. #song-image.ytmusic-player {
  1015. background-color: black;
  1016. }
  1017.  
  1018. ytmusic-player-page:not([player-fullscreened]) #main-panel.style-scope.ytmusic-player-page[style*="padding"] {
  1019. padding: 0px 0px !important;
  1020. box-sizing: border-box;
  1021. }
  1022.  
  1023. ytmusic-player-page:not([player-fullscreened]) ytmusic-player#player.style-scope.ytmusic-player-page {
  1024. max-height: 100%;
  1025. margin-top: calc(-1*var(--ytmusic-player-page-vertical-padding));
  1026. box-sizing: border-box;
  1027. }
  1028.  
  1029. /* #movie_player > .ytp-iv-video-content {
  1030. pointer-events: none; // allow clicking
  1031. } */
  1032.  
  1033. #movie_player > .html5-video-container:not(:empty) {
  1034. box-sizing: border-box;
  1035. height: 100%;
  1036. }
  1037.  
  1038. @keyframes mobileMenuItemAppeared {
  1039. 0% {
  1040. background-position-x: 3px;
  1041. }
  1042. 100% {
  1043. background-position-x: 4px;
  1044. }
  1045. }
  1046. ytm-select.player-speed-settings ~ ytm-menu-item:last-of-type {
  1047. animation: mobileMenuItemAppeared 1ms linear 0s 1 normal forwards;
  1048. }
  1049. @keyframes contextmenuInfoItemAppeared {
  1050. 0% {
  1051. background-position-x: 3px;
  1052. }
  1053. 100% {
  1054. background-position-x: 4px;
  1055. }
  1056. }
  1057. ytmusic-popup-container.ytmusic-app ytmusic-menu-popup-renderer tp-yt-paper-listbox > [role="option"]:first-child {
  1058. animation: contextmenuInfoItemAppeared 1ms linear 0s 1 normal forwards;
  1059. }
  1060. #confirmDialog794 {
  1061. z-index:999999 !important;
  1062. display: none;
  1063. /* Hidden by default */
  1064. position: fixed;
  1065. /* Stay in place */
  1066. z-index: 1;
  1067. /* Sit on top */
  1068. left: 0;
  1069. top: 0;
  1070. width: 100%;
  1071. /* Full width */
  1072. height: 100%;
  1073. /* Full height */
  1074. overflow: auto;
  1075. /* Enable scroll if needed */
  1076. background-color: rgba(0,0,0,0.4);
  1077. /* Black w/ opacity */
  1078. }
  1079. #confirmDialog794 .confirm-box {
  1080. position:relative;
  1081. color: black;
  1082. z-index:999999 !important;
  1083. background-color: #fefefe;
  1084. margin: 15% auto;
  1085. /* 15% from the top and centered */
  1086. padding: 20px;
  1087. border: 1px solid #888;
  1088. width: 30%;
  1089. /* Could be more or less, depending on screen size */
  1090. box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
  1091. }
  1092. #confirmDialog794 .confirm-buttons {
  1093. text-align: right;
  1094. }
  1095. #confirmDialog794 button {
  1096. margin-left: 10px;
  1097. }
  1098. `
  1099. document.head.appendChild(style);
  1100. })
  1101.  
  1102.  
  1103. })();

QingJ © 2025

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