Skip to content

Instantly share code, notes, and snippets.

@juliendargelos
Last active March 21, 2026 09:46
Show Gist options
  • Select an option

  • Save juliendargelos/300e765afa68e4a3530ec8e0849d608c to your computer and use it in GitHub Desktop.

Select an option

Save juliendargelos/300e765afa68e4a3530ec8e0849d608c to your computer and use it in GitHub Desktop.
User Script that forces original audio track on YouTube videos
// ==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)
}
}
@adimuhamad
Copy link
Copy Markdown

adimuhamad commented Nov 20, 2025

It works for me, thanks.

@juliendargelos
Copy link
Copy Markdown
Author

Nice :)

@RegardBattery5
Copy link
Copy Markdown

RegardBattery5 commented Mar 13, 2026

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