Skip to content

Instantly share code, notes, and snippets.

@Nate-Wilkins
Last active October 15, 2023 03:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nate-Wilkins/fddab6116a31de5237e3eb215e37d3d1 to your computer and use it in GitHub Desktop.
Save Nate-Wilkins/fddab6116a31de5237e3eb215e37d3d1 to your computer and use it in GitHub Desktop.
youtube_ad_muter.userscript
// ==UserScript==
// @name CodeNull - YouTube Videos Ad Muter
// @author nate-wilkins@code-null.com
// @version 4.0
// @namespace http://tampermonkey.net/
// @description Detects and blocks ads on YouTube Videos. Automatically mute ads. Turn sound on again after the ad.
// @match https://www.youtube.com/*
// @grant none
// @run-at document-start
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @downloadURL https://gist.githubusercontent.com/Nate-Wilkins/fddab6116a31de5237e3eb215e37d3d1/raw
// @updateURL https://gist.githubusercontent.com/Nate-Wilkins/fddab6116a31de5237e3eb215e37d3d1/raw
// ==/UserScript==
document.addEventListener('DOMContentLoaded', async () => {
const selectorVideo = 'video';
const selectorDivContainerVideo = '.html5-video-player';
const selectorDivAdvertisement = '.ad-showing';
const selectorButtonSkip = '.ytp-ad-skip-button';
const $video = () => document.querySelector(selectorVideo);
const $divContainerVideo = () => document.querySelector(selectorDivContainerVideo);
const $divAdvertisement = () => document.querySelector(selectorDivAdvertisement);
const $buttonSkip = () => document.querySelector(selectorButtonSkip);
const waitFor = (callbackCondition, timeCheck = 100, timeOut = 1000) => {
let timePassed = 0;
let idInterval = null;
return new Promise((resolve, reject) => {
idInterval = setInterval(() => {
timePassed += timeCheck;
if (callbackCondition()) {
clearInterval(idInterval);
resolve();
} else {
if (timePassed >= timeOut) {
reject(new Error("Timed out waiting for condition."));
}
}
}, timeCheck);
});
};
let observer_$divContainerVideo_skipper = null;
let observer_$divContainerVideo_muter = null;
const run = async () => {
// Disconnect.
if (observer_$divContainerVideo_skipper !== null) {
observer_$divContainerVideo_skipper.disconnect();
}
if (observer_$divContainerVideo_muter !== null) {
observer_$divContainerVideo_muter.disconnect();
}
// Wait for Video.
try {
await waitFor($divContainerVideo);
} catch (e) {
setTimeout(run, 3000);
return;
}
// Observe Video for Ads muting.
let previousVolume = null;
observer_$divContainerVideo_muter = new MutationObserver(async () => {
if ($divAdvertisement() && $video() && previousVolume === null) {
try {
previousVolume = $video().volume;
// Mute.
$video().volume = 0;
await waitFor(() => !$divAdvertisement(), 500, 10 * 60 * 1000);
} catch (e) {
console.log("Timedout waiting for Ad to finish.");
} finally {
// UnMute.
$video().volume = previousVolume;
// Cleanup.
previousVolume = null;
}
}
});
observer_$divContainerVideo_muter.observe($divContainerVideo(), {
characterData: true,
childList: true,
attributes: true,
subtree: true
});
// Observe Video for Ads skipping.
observer_$divContainerVideo_skipper = new MutationObserver(async () => {
if ($buttonSkip()) {
// Skip Ad.
$buttonSkip().click();
}
});
observer_$divContainerVideo_skipper.observe($divContainerVideo(), {
characterData: true,
childList: true,
attributes: true,
subtree: true
});
// Do we need to re-listen for Videos?
try {
await waitFor($divContainerVideo);
} catch (e) {
setTimeout(run, 3000);
return;
}
};
await run();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment