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

QingJ © 2025

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