Last active
March 21, 2026 09:46
-
-
Save juliendargelos/300e765afa68e4a3530ec8e0849d608c to your computer and use it in GitHub Desktop.
User Script that forces original audio track on YouTube videos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Force original audio track on YouTube videos | |
| // @match https://youtube.com/* | |
| // @match https://www.youtube.com/* | |
| // ==/UserScript== | |
| const ORIGINAL_AUDIO_TRACK_NAME_PATTERN = /^.+?\([^\)]+\)\s+[^\(\)]+|.+?\s+original$/ | |
| new MutationObserver(debounce(100, update)).observe(document.head, { | |
| childList: true, | |
| subtree: true, | |
| characterData: true | |
| }) | |
| setTimeout(update, 500) | |
| function update() { | |
| for (const element of document.querySelectorAll('ytd-player')) { | |
| useOriginalAudioTrack(element.player_) | |
| } | |
| } | |
| function useOriginalAudioTrack(player) { | |
| const tracks = player.getAvailableAudioTracks() | |
| for (const track of tracks) { | |
| const { name } = track.getLanguageInfo() | |
| if (ORIGINAL_AUDIO_TRACK_NAME_PATTERN.test(name)) { | |
| player.setAudioTrack(track) | |
| break | |
| } | |
| } | |
| } | |
| function debounce(delay, callback) { | |
| let timeout | |
| return (...args) => { | |
| clearTimeout(timeout) | |
| timeout = setTimeout(callback, delay) | |
| } | |
| } |
Author
Nice :)
Hi, thanks for providing the code. I modified the script so it also changes the audio track when the MiniPlayer preview in the search results starts playing (e.g., when hovering over a video).
// ==UserScript==
// @name Force original audio track on YouTube videos
// @match https://youtube.com/*
// @match https://www.youtube.com/*
// ==/UserScript==
const ORIGINAL_AUDIO_TRACK_NAME_PATTERN = /^.+?\([^\)]+\)\s+[^\(\)]+|.+?\s+original$/
new MutationObserver(debounce(100, update)).observe(document.head, {
childList: true,
subtree: true,
characterData: true
})
setTimeout(update, 500)
const debug = false;
function logMessage(...args) {
if (debug) {
console.log(...args)
}
}
const previewObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
const el = mutation.target
logMessage("Element:", el.tagName)
logMessage("Changed attribute:", mutation.attributeName)
logMessage("All attributes:", el.getAttributeNames())
if (
el.tagName === "YTD-VIDEO-PREVIEW" &&
el.hasAttribute("active") &&
el.hasAttribute("playing")
) {
logMessage("Observer Match", el)
update()
}
}
}
})
previewObserver.observe(document.body, {
attributes: true,
subtree: true,
attributeFilter: ["playing"]
})
function update() {
const processedElements = new Set();
for (const element of document.querySelectorAll('ytd-player')) {
var player = element.player_;
logMessage("processedElements:", processedElements);
if (!player) {
player = element.player
}
if (!player) {
logMessage("skip: no player on element", element);
continue;
}
const url = player?.getVideoUrl?.() ?? "";
logMessage("URL:", url);
if (url === "https://www.youtube.com/watch") {
continue
}
if (processedElements.has(url)) {
logMessage("skip: already processed", url);
continue;
}
logMessage("processing:", url);
useOriginalAudioTrack(player);
processedElements.add(url);
logMessage("added to processedElements:", url);
}
}
function useOriginalAudioTrack(player) {
const tracks = player?.getAvailableAudioTracks?.() ?? null
logMessage("Tracks:", tracks)
if (!tracks || tracks.size < 2) {
return
}
for (const track of tracks) {
const {name} = track.getLanguageInfo();
logMessage("Checking track:", name);
if (ORIGINAL_AUDIO_TRACK_NAME_PATTERN.test(name)) {
logMessage("MATCH:", player);
logMessage("Track", track)
track.Z = true;
player.setAudioTrack(track, null, null);
logMessage("SetAudioForUrl:", player.getVideoUrl(), "Name:", name, "Track:", track);
break;
} else {
logMessage("NoMatch for Name:", name);
}
}
}
function debounce(delay, callback) {
let timeout
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(callback, delay)
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It works for me, thanks.