// ==UserScript== // @name YouTube Music: Audio Only // @description No Video Streaming // @namespace UserScript // @version 0.1.14 // @author CY Fung // @match https://music.youtube.com/* // @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/ // @icon https://raw.githubusercontent.com/cyfung1031/userscript-supports/main/icons/YouTube-Audio-Only.png // @grant GM_registerMenuCommand // @grant GM.setValue // @grant GM.getValue // @run-at document-start // @license MIT // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible safari // @allFrames true // // @downloadURL none // ==/UserScript== (async function () { 'use strict'; !window.TTP && (() => { // credit to Benjamin Philipp // original source: https://greasyfork.org/en/scripts/433051-trusted-types-helper // --------------------------------------------------- Trusted Types Helper --------------------------------------------------- const overwrite_default = false; // If a default policy already exists, it might be best not to overwrite it, but to try and set a custom policy and use it to manually generate trusted types. Try at your own risk const prefix = `TTP`; var passThroughFunc = function (string, sink) { return string; // Anything passing through this function will be returned without change } var TTPName = "passthrough"; var TTP_default, TTP = { createHTML: passThroughFunc, createScript: passThroughFunc, createScriptURL: passThroughFunc }; // We can use TTP.createHTML for all our assignments even if we don't need or even have Trusted Types; this should make fallbacks and polyfills easy var needsTrustedHTML = false; function doit() { try { if (typeof window.isSecureContext !== 'undefined' && window.isSecureContext) { if (window.trustedTypes && window.trustedTypes.createPolicy) { needsTrustedHTML = true; if (trustedTypes.defaultPolicy) { log("TT Default Policy exists"); if (overwrite_default) TTP = window.trustedTypes.createPolicy("default", TTP); else TTP = window.trustedTypes.createPolicy(TTPName, TTP); // Is the default policy permissive enough? If it already exists, best not to overwrite it TTP_default = trustedTypes.defaultPolicy; log("Created custom passthrough policy, in case the default policy is too restrictive: Use Policy '" + TTPName + "' in var 'TTP':", TTP); } else { TTP_default = TTP = window.trustedTypes.createPolicy("default", TTP); } log("Trusted-Type Policies: TTP:", TTP, "TTP_default:", TTP_default); } } } catch (e) { log(e); } } function log(...args) { if ("undefined" != typeof (prefix) && !!prefix) args = [prefix + ":", ...args]; if ("undefined" != typeof (debugging) && !!debugging) args = [...args, new Error().stack.replace(/^\s*(Error|Stack trace):?\n/gi, "").replace(/^([^\n]*\n)/, "\n")]; console.log(...args); } doit(); // --------------------------------------------------- Trusted Types Helper --------------------------------------------------- window.TTP = TTP; })(); function createHTML(s) { if (typeof TTP !== 'undefined' && typeof TTP.createHTML === 'function') return TTP.createHTML(s); return s; } let trustHTMLErr = null; try { document.createElement('div').innerHTML = createHTML('1'); } catch (e) { trustHTMLErr = e; } if (trustHTMLErr) { console.log(`trustHTMLErr`, trustHTMLErr); trustHTMLErr(); // exit userscript } /** @type {globalThis.PromiseConstructor} */ const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve. if (typeof AbortSignal === 'undefined') throw new DOMException("Please update your browser.", "NotSupportedError"); async function confirm(message) { // Create the HTML for the dialog if (!document.body) return; let dialog = document.getElementById('confirmDialog794'); if (!dialog) { const dialogHTML = `

${message}

`; // Append the dialog to the document body document.body.insertAdjacentHTML('beforeend', createHTML(dialogHTML)); dialog = document.getElementById('confirmDialog794'); } // Return a promise that resolves or rejects based on the user's choice return new Promise((resolve) => { document.getElementById('confirmBtn').onclick = () => { resolve(true); cleanup(); }; document.getElementById('cancelBtn').onclick = () => { resolve(false); cleanup(); }; function cleanup() { dialog && dialog.remove(); dialog = null; } }); } if (location.pathname === '/live_chat' || location.pathname === 'live_chat_replay') return; const kEventListener = (evt) => { if (document.documentElement.hasAttribute('forceRefresh032')) { evt.stopImmediatePropagation(); evt.stopPropagation(); } } window.addEventListener('beforeunload', kEventListener, false); const pageInjectionCode = function () { if (typeof AbortSignal === 'undefined') throw new DOMException("Please update your browser.", "NotSupportedError"); const URL = window.URL || new Function('return URL')(); const createObjectURL = URL.createObjectURL.bind(URL); /** @type {globalThis.PromiseConstructor} */ const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve. const PromiseExternal = ((resolve_, reject_) => { const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject }; return class PromiseExternal extends Promise { constructor(cb = h) { super(cb); if (cb === h) { /** @type {(value: any) => void} */ this.resolve = resolve_; /** @type {(reason?: any) => void} */ this.reject = reject_; } } }; })(); const createPipeline = () => { let pipelineMutex = Promise.resolve(); const pipelineExecution = fn => { return new Promise((resolve, reject) => { pipelineMutex = pipelineMutex.then(async () => { let res; try { res = await fn(); } catch (e) { console.log(e); reject(e); } resolve(res); }).catch(console.warn); }); }; return pipelineExecution; } const observablePromise = (proc, timeoutPromise) => { let promise = null; return { obtain() { if (!promise) { promise = new Promise(resolve => { let mo = null; const f = () => { let t = proc(); if (t) { mo.disconnect(); mo.takeRecords(); mo = null; resolve(t); } } mo = new MutationObserver(f); mo.observe(document, { subtree: true, childList: true }) f(); timeoutPromise && timeoutPromise.then(() => { resolve(null) }); }); } return promise } } } const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); const prototypeInherit = (d, b) => { const m = Object.getOwnPropertyDescriptors(b); for (const p in m) { if (!Object.getOwnPropertyDescriptor(d, p)) { Object.defineProperty(d, p, m[p]); } } }; let setTimeout_ = setTimeout; let clearTimeout_ = clearTimeout; const delayPn = delay => new Promise((fn => setTimeout_(fn, delay))); const mockEvent = (o, elem) => { o = o || {}; elem = elem || null; return { preventDefault: () => { }, stopPropagation: () => { }, stopImmediatePropagation: () => { }, returnValue: true, target: elem, srcElement: elem, defaultPrevented: false, cancelable: true, timeStamp: performance.now(), ...o } }; const generalRegister = (prop, symbol, checker, pg) => { const objSet = new Set(); let done = false; const f = (o) => { const ct = o.constructor; const proto = ct.prototype; if (!done && proto && ct !== Function && ct !== Object && checker(proto)) { done = true; delete Object.prototype[prop]; objSet.delete(proto); objSet.delete(o); for (const obj of objSet) { obj[prop] = obj[symbol]; delete obj[symbol]; } objSet.clear(); Object.defineProperty(proto, prop, pg); return proto; } return false; }; Object.defineProperty(Object.prototype, prop, { get() { const p = f(this); if (p) { return p[prop]; } else { return this[symbol]; } }, set(nv) { const p = f(this); if (p) { p[prop] = nv; } else { objSet.add(this); this[symbol] = nv; } return true; }, enumerable: false, configurable: true }); }; if (!Object.defineProperty322 && typeof Object.defineProperty === 'function' && Object.defineProperty.length === 3) { // _definePropertyAccessor Object.defineProperty322 = Object.defineProperty; const st = new Set( [ 'videoMode', 'hasAvSwitcher', 'isVideo', 'playbackMode', 'selectedItemHasVideo' ] ); const defineProperty322 = Object.defineProperty322; if (defineProperty322) { Object.defineProperty = function (o, k, t) { if (typeof o.is === 'string') { if (!('configurable' in t) && typeof t.get === 'function' && typeof t.set === 'function') { t.configurable = true; if (st.has(k)) { t.set = function (e) { this._setPendingProperty(k, e, !0) && this._invalidateProperties() } } } } return defineProperty322(o, k, t); } } } const updateLastActiveTimeAsync = (player_) => { // TBC Promise.resolve().then(() => { if (typeof player_.updateLastActiveTime === 'function') { player_.updateLastActiveTime(); } }); }; const attachOneTimeEvent = function (eventType, callback) { let kz = false; document.addEventListener(eventType, function (evt) { if (kz) return; kz = true; callback(evt); }, { capture: true, passive: true, once: true }); } function removeTempObjectProp01() { delete Object.prototype['kevlar_non_watch_unified_player']; delete Object.prototype['kevlar_unified_player']; } function ytConfigFix(config__) { const config_ = config__; if (config_) { const playerKevlar = ((config_ || 0).WEB_PLAYER_CONTEXT_CONFIGS || 0).WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH || 0; if (playerKevlar) { // console.log(322, playerKevlar) playerKevlar.allowWoffleManagement = false; playerKevlar.cinematicSettingsAvailable = false; playerKevlar.showMiniplayerButton = false; playerKevlar.showMiniplayerUiWhenMinimized = false; playerKevlar.transparentBackground = false; playerKevlar.enableCsiLogging = false; playerKevlar.externalFullscreen = false; if (typeof playerKevlar.serializedExperimentFlags === 'string') { playerKevlar.serializedExperimentFlags = ''; // playerKevlar.serializedExperimentFlags = playerKevlar.serializedExperimentFlags.replace(/[-\w]+=(\[\]|[.-\d]+|[_a-z]+|)(&|$)/g,'').replace(/&$/,'') } if (typeof playerKevlar.serializedExperimentIds === 'string') { playerKevlar.serializedExperimentIds = ''; // playerKevlar.serializedExperimentIds = playerKevlar.serializedExperimentIds.replace(/\d+\s*(,\s*|$)/g,'') } } removeTempObjectProp01(); let configs = config_.WEB_PLAYER_CONTEXT_CONFIGS || {}; for (const [key, entry] of Object.entries(configs)) { if (entry && typeof entry.serializedExperimentFlags === 'string' && entry.serializedExperimentFlags.length > 16) { // prevent idle playback failure 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) => { return a + '00' + '=' + b + c; }); } } const EXPERIMENT_FLAGS = config_.EXPERIMENT_FLAGS; if (EXPERIMENT_FLAGS) { EXPERIMENT_FLAGS.kevlar_unified_player = true; EXPERIMENT_FLAGS.kevlar_non_watch_unified_player = true; } const EXPERIMENTS_FORCED_FLAGS = config_.EXPERIMENTS_FORCED_FLAGS; if (EXPERIMENTS_FORCED_FLAGS) { EXPERIMENTS_FORCED_FLAGS.kevlar_unified_player = true; EXPERIMENTS_FORCED_FLAGS.kevlar_non_watch_unified_player = true; } } } Object.defineProperty(Object.prototype, 'kevlar_non_watch_unified_player', { get() { // console.log(501, this.constructor.prototype) return true; }, set(nv) { return true; }, enumerable: false, configurable: true }); Object.defineProperty(Object.prototype, 'kevlar_unified_player', { get() { // console.log(501, this.constructor.prototype) return true; }, set(nv) { return true; }, enumerable: false, configurable: true }); let cw = 0; function fixThumbnailURL(src) { if (typeof src === 'string' && src.length >= 4) { let m = /\b[a-z0-9]{4,13}\.jpg\b/.exec(src); if (m && m[0]) { const t = m[0]; let idx = src.indexOf(t); let nSrc = idx >= 0 ? src.substring(0, idx + t.length) : ''; return nSrc; } } return src; } const avFix = async () => { // if (cw < 6) cw = 6; // const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0; // ytConfigFix(config_); const songImageThumbnail = document.querySelector('#song-image #thumbnail'); if (songImageThumbnail) { if (songImageThumbnail.getAttribute('object-fit') !== 'CONTAIN') songImageThumbnail.setAttribute('object-fit', 'CONTAIN'); mo2.observe(songImageThumbnail, { attributes: true }); const img = HTMLElement.prototype.querySelector.call(songImageThumbnail, 'img#img[src]'); if (img) { mo2.observe(img, { attributes: true }); const src = img.getAttribute('src'); let nSrc = fixThumbnailURL(src); if (nSrc !== src && nSrc && src) { // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg?sqp=-oa&rs=A // https://i.ytimg.com/vi/gcCqclvIcn4/sddefault.jpg img.setAttribute('src', nSrc) } /* iurl: "default.jpg", iurlmq: "mqdefault.jpg", iurlhq: "hqdefault.jpg", iurlsd: "sddefault.jpg", iurlpop1: "pop1.jpg", iurlpop2: "pop2.jpg", iurlhq720: "hq720.jpg", iurlmaxres: "maxresdefault.jpg" */ } } for (const s of document.querySelectorAll('[playback-mode][selected-item-has-video]')) { s.removeAttribute('selected-item-has-video'); } for (const s of document.querySelectorAll('ytmusic-player-page')) { // s.setAttribute('has-av-switcher', '') s.removeAttribute('has-av-switcher') } for (const s of document.querySelectorAll('[video-mode]')) { s.removeAttribute('video-mode') } for (const ytElement of document.querySelectorAll('ytmusic-player-page')) { if (ytElement.is === 'ytmusic-player-page') { mo2.observe(ytElement, { attributes: true }); const cnt = insp(ytElement); const cProto = cnt.constructor.prototype; if (!cProto.setFn322) { cProto.setFn322 = function () { if (this.videoMode === true) this.videoMode = false; // if (this.hasAvSwitcher === false) this.hasAvSwitcher = true; if (this.hasAvSwitcher === true) this.hasAvSwitcher = false; } } if (typeof cProto.computeShowAvSwitcher === 'function' && !cProto.computeShowAvSwitcher322) { cProto.computeShowAvSwitcher322 = cProto.computeShowAvSwitcher; cProto.computeShowAvSwitcher = function () { this.setFn322(); return this.computeShowAvSwitcher322(...arguments); } } cnt.setFn322(); } } for (const ytElement of document.querySelectorAll('ytmusic-av-toggle')) { if (ytElement.is === 'ytmusic-av-toggle') { mo2.observe(ytElement, { attributes: true }); const cnt = insp(ytElement); // cnt.toggleDisabled = false; const cProto = cnt.constructor.prototype; if (!cProto.setFn322) { cProto.setFn322 = function () { if (this.mustPlayAudioOnly === false) this.mustPlayAudioOnly = true; // if(this.isVideo === true) this.isVideo = false; // if(this.playbackMode !== 'ATV_PREFERRED') this.playbackMode = 'ATV_PREFERRED'; if (this.selectedItemHasVideo === true) this.selectedItemHasVideo = false; } } if (typeof cProto.computeToggleDisabled === 'function' && !cProto.computeToggleDisabled322) { cProto.computeToggleDisabled322 = cProto.computeToggleDisabled; cProto.computeToggleDisabled = function () { this.setFn322(); return this.computeToggleDisabled322(...arguments); } } cnt.setFn322(); // cnt.computeToggleDisabled = ()=>{}; // if(cnt.isVideo === true) cnt.isVideo = false; // cnt.mustPlayAudioOnly = false; // cnt.playbackMode = 'ATV_PREFERRED'; // if(cnt.selectedItemHasVideo === true) cnt.selectedItemHasVideo = false; if (!cnt.onVideoAvToggleTap322 && typeof cnt.onVideoAvToggleTap === 'function' && cnt.onVideoAvToggleTap.length === 0) { cnt.onVideoAvToggleTap322 = cnt.onVideoAvToggleTap; cnt.onVideoAvToggleTap = function () { const pr = new Proxy(this, { get(target, prop) { // if (prop === 'mustPlayAudioOnly') return true; // if (prop === 'playbackMode') return 'NONE'; if (prop === 'selectedItemHasVideo') return false; // if (prop === 'isVideo') return false; let v = target[prop]; // if (typeof v === 'function') return () => { }; return v; }, set(target, prop, value) { return true; } }); this.onVideoAvToggleTap322.call(pr); } } if (!cnt.onSongAvToggleTap322 && typeof cnt.onSongAvToggleTap === 'function' && cnt.onSongAvToggleTap.length === 0) { cnt.onSongAvToggleTap322 = cnt.onSongAvToggleTap; cnt.onSongAvToggleTap = function () { const pr = new Proxy(this, { get(target, prop) { // if (prop === 'mustPlayAudioOnly') return true; // if (prop === 'playbackMode') return 'NONE'; if (prop === 'selectedItemHasVideo') return false; // if (prop === 'isVideo') return false; let v = target[prop]; // if (typeof v === 'function') return () => { }; return v; }, set(target, prop, value) { return true; } }); this.onSongAvToggleTap322.call(pr); } } cnt.onSongAvToggleTap(); // cnt.playbackMode = 'ATV_PREFERRED'; } } } const mo = new MutationObserver(() => { if (cw > 0) { cw--; avFix(); } }); mo.observe(document, { childList: true, subtree: true }); const mo2 = new MutationObserver(() => { if (cw < 1) cw = 1; if (cw > 0) { cw--; avFix(); } }); document.addEventListener('fullscreenchange', () => { if (cw < 3) cw = 3; }); document.addEventListener('yt-navigate-start', () => { if (cw < 3) cw = 3; }); document.addEventListener('yt-navigate-finish', () => { if (cw < 6) cw = 6; avFix(); }); document.addEventListener('yt-navigate-cache', () => { if (cw < 3) cw = 3; }); window.addEventListener("updateui", function () { if (cw < 3) cw = 3; }); window.addEventListener("resize", () => { if (cw < 3) cw = 3; }); window.addEventListener("state-navigatestart", function () { if (cw < 3) cw = 3; }); window.addEventListener("state-navigateend", () => { if (cw < 6) cw = 6; avFix(); }) let cv = null; document.addEventListener('durationchange', (evt) => { const target = (evt || 0).target; if (!(target instanceof HTMLMediaElement)) return; const targetClassList = target.classList || 0; const isPlayerVideo = typeof targetClassList.contains === 'function' ? targetClassList.contains('video-stream') && targetClassList.contains('html5-main-video') : false; if (isPlayerVideo) { if (target.readyState === 1 && target.networkState === 2) { target.__spfgs__ = true; if (cv) { cv.resolve(); cv = null; } } else { target.__spfgs__ = false; } if (cw < 6) cw = 6; avFix(); } }, true); (() => { XMLHttpRequest = (() => { const XMLHttpRequest_ = XMLHttpRequest; if ('__xmMc8__' in XMLHttpRequest_.prototype) return XMLHttpRequest_; const url0 = createObjectURL(new Blob([], { type: 'text/plain' })); const c = class XMLHttpRequest extends XMLHttpRequest_ { constructor(...args) { super(...args); } open(method, url, ...args) { let skip = false; if (!url || typeof url !== 'string') skip = true; else if (typeof url === 'string') { let turl = url[0] === '/' ? `.youtube.com${url}` : `${url}`; if (turl.includes('googleads') || turl.includes('doubleclick.net')) { skip = true; } else if (turl.includes('.youtube.com/pagead/')) { skip = true; } else if (turl.includes('.youtube.com/ptracking')) { skip = true; } else if (turl.includes('.youtube.com/api/stats/')) { // /api/stats/ // skip = true; // for user activity logging e.g. watched videos } else if (turl.includes('play.google.com/log')) { skip = true; } else if (turl.includes('.youtube.com//?')) { // //?cpn= skip = true; } } if (!skip) { this.__xmMc8__ = 1; return super.open(method, url, ...args); } else { this.__xmMc8__ = 2; return super.open('GET', url0); } } send(...args) { if (this.__xmMc8__ === 1) { return super.send(...args); } else if (this.__xmMc8__ === 2) { return super.send(); } else { console.log('xhr warning'); return super.send(...args); } } } c.prototype.__xmMc8__ = 0; return c; })(); const s7 = Symbol(); const f7 = () => true; !window.canRetry9048 && generalRegister('canRetry', s7, (p) => { 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' }, { get() { if ('logger' in this && 'policy' in this && 'xhr' && this) { 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" // OKAY ! console.log('canRetry05 - ', this.errorMessage) return f7; } // console.log(this) console.log('canRetry02 - ', this.errorMessage, this) } else { console.log('canRetry ERR - ', this.errorMessage) } return this[s7]; }, set(nv) { this[s7] = nv; return true; }, enumerable: false, configurable: true }); window.canRetry9048 = 1; })(); attachOneTimeEvent('yt-action', function () { const config_ = typeof yt !== 'undefined' ? (yt || 0).config_ : 0; ytConfigFix(config_); }); let prepared = false; function prepare() { if (prepared) return; prepared = true; if (typeof _yt_player !== 'undefined' && _yt_player && typeof _yt_player === 'object') { for (const [k, v] of Object.entries(_yt_player)) { const p = typeof v === 'function' ? v.prototype : 0; if (p && typeof p.clone === 'function' && typeof p.get === 'function' && typeof p.set === 'function' && typeof p.isEmpty === 'undefined' && typeof p.forEach === 'undefined' && typeof p.clear === 'undefined' ) { key = k; } } } if (key) { const ClassX = _yt_player[key]; _yt_player[key] = class extends ClassX { constructor(...args) { if (typeof args[0] === 'string' && args[0].startsWith('http://')) args[0] = ''; super(...args); } } _yt_player[key].luX1Y = 1; prototypeInherit(_yt_player[key].prototype, ClassX.prototype); } } let s3 = Symbol(); generalRegister('deviceIsAudioOnly', s3, (p) => { return typeof p.getPlayerType === 'function' && typeof p.getVideoEmbedCode === 'function' && typeof p.getVideoUrl === 'function' && !p.onCueRangeEnter && !p.getVideoData && !('ATTRIBUTE_NODE' in p) }, { get() { return this[s3]; }, set(nv) { if (typeof nv === 'boolean') this[s3] = true; else this[s3] = undefined; prepare(); return true; }, enumerable: false, configurable: true }); let s1 = Symbol(); let s2 = Symbol(); Object.defineProperty(Object.prototype, 'defraggedFromSubfragments', { get() { // console.log(501, this.constructor.prototype) return undefined; }, set(nv) { return true; }, enumerable: false, configurable: true }); Object.defineProperty(Object.prototype, 'hasSubfragmentedFmp4', { get() { // console.log(502, this.constructor.prototype) return this[s1]; }, set(nv) { if (typeof nv === 'boolean') this[s1] = false; else this[s1] = undefined; return true; }, enumerable: false, configurable: true }); Object.defineProperty(Object.prototype, 'hasSubfragmentedWebm', { get() { // console.log(503, this.constructor.prototype) return this[s2]; }, set(nv) { if (typeof nv === 'boolean') this[s2] = false; else this[s2] = undefined; return true; }, enumerable: false, configurable: true }); const supportedFormatsConfig = () => { function typeTest(type) { if (typeof type === 'string' && type.startsWith('video/')) { return false; } } // return a custom MIME type checker that can defer to the original function function makeModifiedTypeChecker(origChecker) { // Check if a video type is allowed return function (type) { let res = undefined; if (type === undefined) res = false; else { res = typeTest.call(this, type); } if (res === undefined) res = origChecker.apply(this, arguments); return res; }; } // Override video element canPlayType() function const proto = (HTMLVideoElement || 0).prototype; if (proto && typeof proto.canPlayType == 'function') { proto.canPlayType = makeModifiedTypeChecker(proto.canPlayType); } // Override media source extension isTypeSupported() function const mse = window.MediaSource; // Check for MSE support before use if (mse && typeof mse.isTypeSupported == 'function') { mse.isTypeSupported = makeModifiedTypeChecker(mse.isTypeSupported); } }; supportedFormatsConfig(); } const isEnable = (typeof GM !== 'undefined' && typeof GM.getValue === 'function') ? (await GM.getValue("isEnable_aWsjF", true)) : null; if (typeof isEnable !== 'boolean') throw new DOMException("Please Update your browser", "NotSupportedError"); if (isEnable) { const element = document.createElement('button'); element.setAttribute('onclick', createHTML(`(${pageInjectionCode})()`)); element.click(); } GM_registerMenuCommand(`Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`, async function () { await GM.setValue("isEnable_aWsjF", !isEnable); location.reload(); }); let messageCount = 0; let busy = false; window.addEventListener('message', (evt) => { const v = ((evt || 0).data || 0).ZECxh; if (typeof v === 'boolean') { if (messageCount > 1e9) messageCount = 9; const t = ++messageCount; if (v && isEnable) { requestAnimationFrame(async () => { if (t !== messageCount) return; if (busy) return; busy = true; if (await confirm("Livestream is detected. Press OK to disable YouTube Audio Mode.")) { await GM.setValue("isEnable_aWsjF", !isEnable); location.reload(); } busy = false; }); } } }); const pLoad = new Promise(resolve => { if (document.readyState !== 'loading') { resolve(); } else { window.addEventListener("DOMContentLoaded", resolve, false); } }); function contextmenuInfoItemAppearedFn(target) { const btn = target.closest('[role="option"]'); if (!btn) return; if (btn.parentNode.querySelector('[role="option"].audio-only-toggle-btn')) return; document.documentElement.classList.add('with-audio-only-toggle-btn'); const newBtn = btn.cloneNode(true); const h = () => { newBtn.classList.remove('iron-selected'); newBtn.classList.remove('focused'); newBtn.removeAttribute('iron-selected'); newBtn.removeAttribute('focused'); let a = newBtn.querySelector('a'); if (a) a.removeAttribute('href'); newBtn.classList.add('audio-only-toggle-btn'); } h(); async function reloadPage() { await GM.setValue("isEnable_aWsjF", !isEnable); document.documentElement.setAttribute('forceRefresh032', ''); location.reload(); } newBtn.addEventListener('click', (evt) => { evt.preventDefault(); evt.stopImmediatePropagation(); evt.stopPropagation(); reloadPage(); }, true); btn.parentNode.insertBefore(newBtn, null); // let t; // let h = 0; // t = btn.closest('.ytp-panel-menu[style*="height"]'); // if (t) t.style.height = t.scrollHeight + 'px'; // t = btn.closest('.ytp-panel[style*="height"]'); // if (t) t.style.height = (h = t.scrollHeight) + 'px'; // t = btn.closest('.ytp-popup.ytp-contextmenu[style*="height"]'); // if (t && h > 0) t.style.height = h + 'px'; const f = () => { h(); const mx = newBtn.querySelector('yt-formatted-string'); if (mx) { mx.removeAttribute('is-empty'); mx.textContent = `Turn ${isEnable ? 'OFF' : 'ON'} YouTube Audio Mode`; } let t; t = btn.closest('ytmusic-menu-popup-renderer[style*="max-height"]'); if (t) t.style.maxHeight = t.scrollHeight + 'px'; } f(); setTimeout(f, 40); } function mobileMenuItemAppearedFn(target) { } pLoad.then(() => { document.addEventListener('animationstart', (evt) => { const animationName = evt.animationName; if (!animationName) return; if (animationName === 'contextmenuInfoItemAppeared') contextmenuInfoItemAppearedFn(evt.target); if (animationName === 'mobileMenuItemAppeared') mobileMenuItemAppearedFn(evt.target); }, true); const style = document.createElement('style'); style.id = 'fm9v0'; style.textContent = ` .html5-video-player { background-color: black; } #song-image.ytmusic-player { background-color: black; } ytmusic-player-page:not([player-fullscreened]) #main-panel.style-scope.ytmusic-player-page[style*="padding"] { padding: 0px 0px !important; box-sizing: border-box; } ytmusic-player-page:not([player-fullscreened]) ytmusic-player#player.style-scope.ytmusic-player-page { max-height: 100%; margin-top: calc(-1*var(--ytmusic-player-page-vertical-padding)); box-sizing: border-box; } /* #movie_player > .ytp-iv-video-content { pointer-events: none; // allow clicking } */ #movie_player > .html5-video-container:not(:empty) { box-sizing: border-box; height: 100%; } @keyframes mobileMenuItemAppeared { 0% { background-position-x: 3px; } 100% { background-position-x: 4px; } } ytm-select.player-speed-settings ~ ytm-menu-item:last-of-type { animation: mobileMenuItemAppeared 1ms linear 0s 1 normal forwards; } @keyframes contextmenuInfoItemAppeared { 0% { background-position-x: 3px; } 100% { background-position-x: 4px; } } ytmusic-popup-container.ytmusic-app ytmusic-menu-popup-renderer tp-yt-paper-listbox > [role="option"]:first-child { animation: contextmenuInfoItemAppeared 1ms linear 0s 1 normal forwards; } #confirmDialog794 { z-index:999999 !important; display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } #confirmDialog794 .confirm-box { position:relative; color: black; z-index:999999 !important; background-color: #fefefe; margin: 15% auto; /* 15% from the top and centered */ padding: 20px; border: 1px solid #888; width: 30%; /* Could be more or less, depending on screen size */ box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); } #confirmDialog794 .confirm-buttons { text-align: right; } #confirmDialog794 button { margin-left: 10px; } ` document.head.appendChild(style); }) })();