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

QingJ © 2025

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