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

QingJ © 2025

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