Disable DRC Audio on YouTube (+ both tweaks)

The script disables DRC audio (aka Stable Volume) on YouTube (along with both volume normalization disabled and prevents automatic dubbing on all videos).

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Disable DRC Audio on YouTube (+ both tweaks)
// @author       LegendCraft (forked from Adri and The0x539)
// @namespace    Tampermonkey Scripts
// @match        https://www.youtube.com/*
// @grant        none
// @version      2025.11.03
// @description  The script disables DRC audio (aka Stable Volume) on YouTube (along with both volume normalization disabled and prevents automatic dubbing on all videos).
// @license      MIT
// @run-at       document-idle
// ==/UserScript==
// Disables the 'Stable volume' feature
/* jshint esversion: 11 */
 
function waitForElement(selector) {
	return new Promise((resolve, reject) => {
		let element = document.querySelector(selector);
		if (element) {
			resolve(element);
			return;
		}
 
		const observer = new MutationObserver(mutations => {
			const element = document.querySelector(selector);
			if (element) {
				observer.disconnect();
				resolve(element);
			}
		});
		observer.observe(document.body, {
			childList: true,
			subtree: true
		});
	});
}
 
async function disableDRC() {
	const menuButton = await waitForElement('.ytp-settings-button');
 
	menuButton.click();
	menuButton.click();
 
	const drcMenuItem = await waitForElement('.ytp-drc-menu-item:not([aria-disabled])');
 
	if (drcMenuItem.getAttribute('aria-checked') === 'true') {
		drcMenuItem.click();
		console.log('Disabled DRC Audio');
	} else {
		console.log('DRC Audio is already disabled');
	}
}
 
disableDRC().catch(error => console.error('Error:', error));

// Disable 'Loudness normalization' feature (first introduced back in Dec 2014)
function ytvt_hook() {
  function ytvt_patchDescriptor(targetProto, targetProp) {
    let desc = Object.getOwnPropertyDescriptor(targetProto, targetProp);
    Object.defineProperty(targetProto, targetProp, {
      enumerable: true,
      configurable: true,
      get: function() { return desc.get.call(this); },
      set: function(e) { void e; }
    });
    return desc;
  }
 
  let ytvt_desc = ytvt_patchDescriptor(HTMLMediaElement.prototype, 'volume');
  let ytvt_valSetter = HTMLElement.prototype.setAttribute;
  HTMLElement.prototype.setAttribute = function(name, val) {
    if(name === 'aria-valuenow') {
      let isVolume = this.matches('[aria-label="Volume"][role="slider"]');
      if(isVolume) {
        let newVolume = val / 100;
        let players = document.querySelectorAll('.video-stream');
        // players[1] can be the active, so just set the volume on all players
        for(let player of players) {
          if(player.volume !== newVolume) {
            ytvt_desc.set.call(player, newVolume);
          }
        }
      }
    }
    ytvt_valSetter.call(this, name, val);
  };
 
  let ytvt_rule = '.ytp-sfn-content::after, ytmusic-nerd-stats::after { content: "using No Volume Normalization" }';
  let ytvt_sheet = new CSSStyleSheet();
  ytvt_sheet.insertRule(ytvt_rule);
  document.adoptedStyleSheets = (document.adoptedStyleSheets || []).concat([ytvt_sheet]);
 
  let ytvt_script = document.getElementById('ytvoltweak');
  if(ytvt_script) { ytvt_script.remove(); }
}
 
function ytvt_inject() {
  let ytvt_textContent = ytvt_hook + '\n' + ytvt_hook.name + '();';
  GM_addElement('script', {textContent: ytvt_textContent, id: 'ytvoltweak'});
}
 
if(!document.head) {
  let ytvt_headObserver = new MutationObserver(function(_arr, obs) {
    if(document.head) { obs.disconnect(); ytvt_inject(); }
  });
  ytvt_headObserver.observe(document, {subtree: true, childList: true});
} else {
  ytvt_inject();
}

// Prevent to force the 'Auto-dubbing' feature (for all videos that have any conturies)
/*jshint esversion: 11 */
 
(function () {
    'use strict';
 
    function fallbackGetPlayer() {
        if (window.location.hostname === 'm.youtube.com') return document.querySelector('#movie_player');
        if (window.location.pathname.startsWith('/shorts')) return document.querySelector('#shorts-player');
        if (window.location.pathname.startsWith('/watch')) return document.querySelector('#movie_player');
        return document.querySelector('.inline-preview-player');
    }
 
    function main(event) {
        try {
            const getTrackId = (track) => Object.values(track ?? {}).find((p) => p?.id)?.id ?? null;
            const player = event?.target?.player_ ?? fallbackGetPlayer();
            const availableTracks = player.getAvailableAudioTracks();
            if (availableTracks?.length === 0) return; // Either no dubbing or YouTube's API failed.
            const dubAudioTrack = player.getAudioTrack();
            if (`${dubAudioTrack}` === 'Default') return; // YouTube sometimes returns a partially populated object named "Default" if already using the original language. Perhaps a bug on YouTube's side?
            const renderer = player.getPlayerResponse()?.captions?.playerCaptionsTracklistRenderer;
            const originalAudioId = renderer?.audioTracks?.[renderer?.defaultAudioTrackIndex]?.audioTrackId;
            if (!originalAudioId || getTrackId(dubAudioTrack) === originalAudioId) return; // No undo necessary so return early.
            console.log('Auto-dub detected, trying to undo...');
            const originalAudioTrack = availableTracks.find((track) => getTrackId(track) === originalAudioId);
            if (!originalAudioTrack) throw new Error('Unable to determine the original audio track.');
            player.setAudioTrack(originalAudioTrack);
            console.log(`Auto-dub undo successful. Audio track reverted from ${dubAudioTrack} to ${originalAudioTrack}.`);
        } catch (error) {
            console.error('Failed to prevent YouTube auto-dubbing.', error);
        }
    }
 
    const playerUpdateEvent = window.location.hostname === 'm.youtube.com' ? 'state-navigateend' : 'yt-player-updated';
    window.addEventListener(playerUpdateEvent, main, true);
    window.addEventListener('pageshow', main, {once: true});
})();

// Add CSS to remove the 'Stable volume' option from the right menu (legacy code included)
(function() {
let css = `.ytp-panel .ytp-menuitem.ytp-drc-menu-item { display: none !important }`;
if (typeof GM_addStyle !== "undefined") {
  GM_addStyle(css);
} else {
  let styleNode = document.createElement("style");
  styleNode.appendChild(document.createTextNode(css));
  (document.querySelector("head") || document.documentElement).appendChild(styleNode);
}
})();

(function() {
    'use strict';
    const style = document.createElement('style');
    style.type = 'text/css';
    style.innerText = '.ytp-panel .ytp-menuitem.ytp-drc-menu-item{display:none;}';
    document.head.appendChild(style);
})();