Automatically mutes and hides the Twitch player when an advertisement started and revert it back to normal once finished. The stream can still be heard during ads. You can also display ads via the "Ads Options" button.
< Feedback on Twitch - Mute and hide ads, while keeping the audio of the stream on
You know you can do a fork of the script right? It's under MIT license, so feel free to publish it as long as you mention this script as the base.
this is fix . i hope its for long. 1.18
// v1.18 20-10-2025 //
(function() {
var _tmuteVars = { "timerCheck": 200, // EDITABLE
"adInProgress": false,
"adsDisplayed": 0,
"disableDisplay": true, // EDITABLE true / false
"anticipatePreroll": false, // EDITABLE
"anticipateTimer": 18000, // EDITABLE
"anticipateInProgress": false,
"anticipatePrematureEnd": false,
"alreadyMuted": false,
"adElapsedTime": undefined,
"adUnlockAt": 270, // EDITABLE
"adMinTime": 1, // EDITABLE
"playerIdAds": 0,
"displayingOptions": false,
"highwindPlayer": undefined,
"currentPage": undefined,
"currentChannel": undefined,
"optionsInitialized": false,
"optionsInitializing": false,
"volumePremute": undefined,
"wasMutedBeforeAd": false,
"wasMutedBeforeAnticipate": false,
"restorePiP": false
};
const VOLUME_KEY = 'twitch__Player____7BTVFZ___Volume';
function getStoredVolume(callback) {
if (!chrome || !chrome.storage) {
callback(0.3);
return;
}
chrome.storage.sync.get([VOLUME_KEY], (result) => {
const volume = result[VOLUME_KEY] !== undefined ? parseFloat(result[VOLUME_KEY]) : 0.3;
callback(volume);
});
}
function setStoredVolume(volume) {
if (!chrome || !chrome.storage) return;
chrome.storage.sync.set({ [VOLUME_KEY]: volume });
}
var _tmuteSelectors = { "hw": { "player": "video-player__container",
"playerVideo": ".video-player__overlay",
"playerDuringAd": "pbyp-player-instance",
"playerHidingDuringAd": "picture-by-picture-player--collapsed",
"muteButton": "button[data-a-target='player-mute-unmute-button']",
"volumeSlider": "input[data-a-target='player-volume-slider']",
"adNotice": undefined,
"adNoticeFinder": "[data-a-target='ax-overlay']",
"viewersCount": "metadata-layout__support"
}
};
var currentSelector = undefined;
const HIDE_CLASS = '__ad_video_hidden_by_script';
const DEBOUNCE_MS = 180;
const CHECK_INTERVAL_MS = 1000;
const SELECTOR_AD_BUTTON = 'button[aria-label*="Learn more about this ad" i], button:has([data-a-target="tw-core-button-label-text"])';
const AD_LABEL_SELECTORS = [
'[data-a-target="video-ad-label"]',
'[data-test-selector="ad-banner-default-text"]',
'[data-a-target="video-ad-countdown"]'
];
const VIDEO_CONTAINER_SELECTORS = [
'.video-player__container',
'.player-video__container',
'.persistent-player',
'[data-a-target="video-player"]',
'#player',
'#channel-player'
];
function debounce(fn, ms = DEBOUNCE_MS) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), ms);
};
}
// функцию restartCheckInterval сразу после Функции debounce:
function restartCheckInterval() {
if (_tmuteVars.autoCheck) clearInterval(_tmuteVars.autoCheck);
_tmuteVars.autoCheck = setInterval(evaluateAdState, _tmuteVars.timerCheck);
if (_tmuteVars.autoUpdate) clearInterval(_tmuteVars.autoUpdate);
_tmuteVars.autoUpdate = setInterval(initUpdate, _tmuteVars.timerCheck);
if (intervalId) clearInterval(intervalId);
startFallback();
}
function elementLooksLikeAdButton(el) {
if (!el || el.nodeType !== 1) return false;
const aria = el.getAttribute && (el.getAttribute('aria-label') || '');
if (aria && /learn.*ad/i.test(aria)) return true;
const txt = (el.innerText || el.textContent || '').trim();
if (txt && /learn\s*more/i.test(txt)) return true;
const dataTarget = el.getAttribute && el.getAttribute('data-a-target');
if (dataTarget && /tw-core-button-label-text|ad/i.test(dataTarget)) return true;
return false;
}
function findAdButton() {
const byAria = document.querySelectorAll('button[aria-label]');
for (const b of byAria) {
const aria = b.getAttribute('aria-label') || '';
if (/learn.*ad/i.test(aria)) return b;
}
const allButtons = document.querySelectorAll(SELECTOR_AD_BUTTON);
for (const b of allButtons) {
if (elementLooksLikeAdButton(b)) return b;
}
const candidate = document.querySelector('button.ScCoreButton-sc-ocjdkq-0');
if (candidate && elementLooksLikeAdButton(candidate)) return candidate;
return null;
}
function findAdLabel() {
for (const s of AD_LABEL_SELECTORS) {
const el = document.querySelector(s);
if (el) return el;
}
const spans = document.querySelectorAll('span,div');
for (const sp of spans) {
const txt = (sp.innerText || sp.textContent || '').trim();
if (/Ad\s*\(\d+:\d+\)|ad break|this ad/i.test(txt)) return sp;
}
return null;
}
// функцию getAdNoticeElement сразу после Функции findAdLabel:
function getAdNoticeElement() {
if (currentSelector.adNotice === undefined) {
const finderEl = document.querySelector(_tmuteSelectors.hw.adNoticeFinder);
if (finderEl) {
currentSelector.adNotice = finderEl.parentNode.className;
console.log('[ad-hide] Ad notice class set: ' + currentSelector.adNotice);
} else {
return null;
}
}
const elements = document.getElementsByClassName(currentSelector.adNotice);
return elements.length > 0 ? elements[0] : null;
}
function collectVideos() {
return Array.from(document.querySelectorAll('video')).filter(Boolean);
}
function findSafeContainerForVideo(video) {
if (!video) return null;
for (const sel of VIDEO_CONTAINER_SELECTORS) {
const anc = video.closest(sel);
if (anc) {
if (anc.querySelector && anc.querySelector('[data-a-target="player-controls"], .player-controls')) {
console.log('[ad-hide] container contains controls, skip');
return null;
}
return anc;
}
}
return null;
}
function applyHide() {
const videos = collectVideos();
if (!videos.length) return;
videos.forEach(v => {
const safeContainer = findSafeContainerForVideo(v);
if (safeContainer) {
safeContainer.classList.add(HIDE_CLASS);
console.log('[ad-hide] hidden container', safeContainer);
} else {
v.classList.add(HIDE_CLASS);
console.log('[ad-hide] hidden video', v);
}
});
}
function removeHide() {
document.querySelectorAll('.' + HIDE_CLASS).forEach(node => node.classList.remove(HIDE_CLASS));
collectVideos().forEach(v => v.classList.remove(HIDE_CLASS));
console.log('[ad-hide] removed hide');
}
// Функции getMainPlayerContainer, getMainVideo, getMuteButton, muteMainPlayer, unmuteMainPlayer сразу после Функции removeHide:
function getMainPlayerContainer() {
for (const sel of VIDEO_CONTAINER_SELECTORS) {
const cont = document.querySelector(sel);
if (cont && cont.querySelector('video')) return cont;
}
return null;
}
function getMainVideo() {
const cont = getMainPlayerContainer();
return cont ? cont.querySelector('video') : document.querySelector('video');
}
function getMuteButton() {
return document.querySelector(currentSelector.muteButton);
}
function muteMainPlayer() {
const mainVideo = getMainVideo();
if (!mainVideo) return;
const muteBtn = getMuteButton();
if (muteBtn && !mainVideo.muted) {
muteBtn.click();
}
mainVideo.muted = true;
mainVideo.volume = 0;
observeVolumeChanges(mainVideo);
}
function unmuteMainPlayer() {
const mainVideo = getMainVideo();
if (!mainVideo) return;
const muteBtn = getMuteButton();
if (muteBtn && mainVideo.muted) {
muteBtn.click();
}
getStoredVolume((storedVolume) => {
mainVideo.volume = storedVolume;
mainVideo.muted = false;
});
}
// *** ПОПАП (БЕЗ ИЗМЕНЕНИЙ) ***
let popupOpen = false;
function closePopup() {
const popup = document.getElementById('_tmads_popup');
if (popup) popup.remove();
popupOpen = false;
document.removeEventListener('click', closePopupHandler);
}
function closePopupHandler(event) {
if (!document.getElementById('_tmads_popup')?.contains(event.target)) {
closePopup();
}
}
function createPopup() {
const popup = document.createElement('div');
popup.id = '_tmads_popup';
popup.innerHTML = `
Ads Options
Timer Check (ms):
Disable Display
Anticipate Preroll
Anticipate Timer (ms):
Ad Unlock At (s):
Ad Min Time (s):
Unlock Player
Close
`;
document.body.appendChild(popup);
document.getElementById('tm_timerCheck').addEventListener('input', debounce((e) => {
_tmuteVars.timerCheck = parseInt(e.target.value);
restartCheckInterval();
}, 300));
document.getElementById('tm_disableDisplay').addEventListener('change', (e) => _tmuteVars.disableDisplay = e.target.checked);
document.getElementById('tm_anticipatePreroll').addEventListener('change', (e) => _tmuteVars.anticipatePreroll = e.target.checked);
document.getElementById('tm_anticipateTimer').addEventListener('input', debounce((e) => _tmuteVars.anticipateTimer = parseInt(e.target.value), 300));
document.getElementById('tm_adUnlockAt').addEventListener('input', debounce((e) => _tmuteVars.adUnlockAt = parseInt(e.target.value), 300));
document.getElementById('tm_adMinTime').addEventListener('input', debounce((e) => _tmuteVars.adMinTime = parseInt(e.target.value), 300));
document.getElementById('tm_unlock').addEventListener('click', () => adsOptions('unlock'));
document.getElementById('tm_close').addEventListener('click', closePopup);
document.addEventListener('click', closePopupHandler);
popupOpen = true;
}
// Замените функцию evaluateAdState целиком на эту:
function evaluateAdState() {
if (_tmuteVars.adInProgress && _tmuteVars.adElapsedTime !== undefined) {
_tmuteVars.adElapsedTime += _tmuteVars.timerCheck / 1000;
const advert = getAdNoticeElement();
if (advert && _tmuteVars.adElapsedTime >= _tmuteVars.adUnlockAt && advert.childElementCount > 2) {
for (let i = advert.childElementCount - 1; i >= 0; i--) {
const child = advert.children[i];
if (!child.classList.contains(currentSelector.adNotice)) {
child.remove();
}
}
console.log('[ad] Unlocked ad notice');
}
}
const btn = findAdButton();
const lbl = findAdLabel();
const advert = getAdNoticeElement();
const hasAdNotice = advert && advert.childElementCount > 2;
const active = hasAdNotice || Boolean(btn || lbl);
if (active && !_tmuteVars.adInProgress) {
if (_tmuteVars.anticipateInProgress !== false) {
clearTimeout(_tmuteVars.anticipateInProgress);
_tmuteVars.anticipateInProgress = false;
}
const mainVideo = getMainVideo();
if (!mainVideo) return;
_tmuteVars.volumePremute = mainVideo.volume;
_tmuteVars.wasMutedBeforeAd = mainVideo.muted;
_tmuteVars.adInProgress = true;
_tmuteVars.adElapsedTime = 0;
_tmuteVars.adsDisplayed++;
console.log('[ad] start #' + _tmuteVars.adsDisplayed);
muteMainPlayer();
actionHidePlayer(true);
unmuteAdPlayer();
} else if (!active && _tmuteVars.adInProgress) {
if (_tmuteVars.adElapsedTime !== undefined && _tmuteVars.adElapsedTime < _tmuteVars.adMinTime) {
return;
}
console.log('[ad] end');
_tmuteVars.adInProgress = false;
_tmuteVars.adElapsedTime = undefined;
if (!_tmuteVars.wasMutedBeforeAd) {
unmuteMainPlayer();
}
actionHidePlayer(false);
}
}
function startFallback() {
if (intervalId) clearInterval(intervalId);
intervalId = setInterval(evaluateAdState, CHECK_INTERVAL_MS);
}
// функцию startDomObserver сразу после// Функции startFallback:
function startDomObserver() {
const observer = new MutationObserver(debounce((mutations) => {
let shouldCheck = false;
for (const mut of mutations) {
for (const node of mut.addedNodes) {
if (node.nodeType !== 1) continue;
if (node.matches(SELECTOR_AD_BUTTON) || (node.querySelector && node.querySelector(SELECTOR_AD_BUTTON))) {
shouldCheck = true;
}
if (node.matches('[data-a-target="ax-overlay"]') || (node.querySelector && node.querySelector('[data-a-target="ax-overlay"]'))) {
shouldCheck = true;
}
for (const s of AD_LABEL_SELECTORS) {
if (node.matches(s) || (node.querySelector && node.querySelector(s))) {
shouldCheck = true;
}
}
}
}
if (shouldCheck) evaluateAdState();
}, 100));
observer.observe(document.body, { childList: true, subtree: true });
}
// Замените функцию unmuteAdPlayer целиком на эту:
function unmuteAdPlayer(firstCall = true) {
const playerDuringAd = document.getElementsByClassName("pbyp-player-instance")[0];
if (playerDuringAd) {
const pipVideo = playerDuringAd.childNodes[0];
observeVolumeChanges(pipVideo);
pipVideo.setAttribute("controls", true);
pipVideo.muted = true;
if (!_tmuteVars.wasMutedBeforeAd) {
pipVideo.volume = _tmuteVars.volumePremute;
pipVideo.muted = false;
}
if (_tmuteVars.restorePiP) pipVideo.requestPictureInPicture();
const playerHidden = document.getElementsByClassName("picture-by-picture-player--collapsed")[0];
if (playerHidden) playerHidden.classList.remove("picture-by-picture-player--collapsed");
observePipAppearance(pipVideo);
} else if (firstCall) {
setTimeout(() => unmuteAdPlayer(false), 2000);
}
}
// функцию observePipAppearance целиком на эту:
function observePipAppearance(pipVideo) {
const observer = new MutationObserver(() => {
if (pipVideo.readyState >= 1) {
pipVideo.volume = _tmuteVars.volumePremute;
observeVolumeChanges(pipVideo);
pipVideo.muted = _tmuteVars.wasMutedBeforeAd;
}
});
observer.observe(pipVideo, { attributes: true, attributeFilter: ['src'] });
}
function observeVolumeChanges(videoElement) {
if (!videoElement) return;
videoElement.addEventListener('volumechange', () => {
setStoredVolume(videoElement.volume);
});
}
// Замените функцию anticipatePreroll целиком на эту:
function anticipatePreroll(initCall = true) {
if (!_tmuteVars.anticipatePreroll || (_tmuteVars.anticipateInProgress !== false && initCall)) return;
const mainVideo = getMainVideo();
if (!mainVideo) return;
_tmuteVars.wasMutedBeforeAnticipate = mainVideo.muted;
muteMainPlayer();
actionHidePlayer(true);
if (initCall) {
_tmuteVars.anticipateInProgress = setTimeout(() => {
anticipatePreroll(false);
}, _tmuteVars.anticipateTimer);
} else {
if (_tmuteVars.adInProgress) {
_tmuteVars.anticipateInProgress = false;
return;
}
if (!_tmuteVars.wasMutedBeforeAnticipate) {
getStoredVolume((storedVolume) => {
const mainVideo = getMainVideo();
if (mainVideo) {
mainVideo.volume = storedVolume;
mainVideo.muted = false;
}
});
}
actionHidePlayer(false);
_tmuteVars.anticipateInProgress = false;
}
}
function actionHidePlayer(hideIt = true) {
if (_tmuteVars.disableDisplay === true) {
if (hideIt) applyHide();
else removeHide();
togglePiP(); // оставляем, если нужно
}
}
function addHideCSS() {
if (!document.getElementById('ad-hide-style')) {
const style = document.createElement('style');
style.id = 'ad-hide-style';
style.textContent = `
video.${HIDE_CLASS}, .${HIDE_CLASS} video,
.${HIDE_CLASS}.video-player__container,
.${HIDE_CLASS}.player-video__container,
.${HIDE_CLASS}.persistent-player {
display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important;
}
.${HIDE_CLASS} [data-a-target="player-controls"], .${HIDE_CLASS} .player-controls {
display: initial !important; visibility: visible !important; opacity: 1 !important; pointer-events: auto !important;
}
`;
document.head.appendChild(style);
}
}
function togglePiP() {
if (document.pictureInPictureElement) {
_tmuteVars.restorePiP = true;
document.exitPictureInPicture();
} else if (_tmuteVars.restorePiP === true && document.pictureInPictureEnabled) {
_tmuteVars.restorePiP = false;
document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].requestPictureInPicture();
}
}
// *** ГЛАВНОЕ ИСПРАВЛЕНИЕ: MutationObserver для кнопки ***
function initAdsOptions() {
// *** ПРЯМАЯ ПРОВЕРКА: если кнопка уже есть — выходим ***
if (document.getElementById("_tmads_showoptions")) {
_tmuteVars.optionsInitialized = true;
return;
}
// *** НАХОДИМ ЛИБО СОЗДАЕМ wrapper в metadata-layout__support ***
let wrapper = document.getElementById("_tmads_wrapper");
const supportContainer = document.querySelector(".metadata-layout__support");
if (!wrapper && supportContainer) {
// Создаем wrapper, если его нет
wrapper = document.createElement("div");
wrapper.id = "_tmads_wrapper";
supportContainer.appendChild(wrapper);
console.log("📦 Создан новый wrapper");
}
if (!wrapper) {
console.log("❌ НЕ НАЙДЕН КОНТЕЙНЕР supportContainer");
setTimeout(initAdsOptions, 500);
return;
}
// *** СОЗДАЕМ СТИЛЬ (если нет) ***
if (!document.getElementById('_tmads_style')) {
const style = document.createElement('style');
style.id = '_tmads_style';
style.textContent = `
._tmads_button {
display: inline-flex !important;
align-items: center;
justify-content: center;
padding: 0 8px !important;
margin-left: 2px !important;
height: 30px !important;
background: #ffffff30 !important;
color: #ffffffd1 !important;
border: 1px solid #fefefe00 !important;
border-radius: 4px !important;
cursor: pointer !important;
font-size: 12px !important;
}
._tmads_button:hover {
background: rgb(181 243 234 / 44%) !important;
color: #b8eaf6 !important;
scale: 1.1 !important;
}
`;
document.head.appendChild(style);
}
// *** ДОБАВЛЯЕМ КНОПКУ В wrapper ***
wrapper.innerHTML = `Ads Options`;
// *** ОБРАБОТЧИК ***
document.getElementById("_tmads_showoptions").addEventListener("click", function(e) {
e.stopPropagation();
e.preventDefault();
if (popupOpen) {
closePopup();
} else {
createPopup();
}
});
_tmuteVars.optionsInitialized = true;
console.log("✅ КНОПКА ДОБАВЛЕНА В wrapper");
// *** MutationObserver для пересоздания ***
const observer = new MutationObserver((mutations) => {
if (!document.getElementById("_tmads_showoptions")) {
console.log("🔄 Кнопка удалена, пересоздаю...");
_tmuteVars.optionsInitialized = false;
setTimeout(initAdsOptions, 100);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Замените функцию adsOptions на эту (только unlock изменен):
function adsOptions(type) {
if (type === "unlock") {
const advert = getAdNoticeElement();
if (!advert || advert.childElementCount <= 2) {
alert("Нет рекламы для разблокировки");
} else {
_tmuteVars.adElapsedTime = _tmuteVars.adUnlockAt;
console.log("🔓 Разблокировка");
}
}
}
// Замените функцию initUpdate целиком на эту:
function initUpdate() {
if (window.location.pathname !== _tmuteVars.currentPage) {
if (_tmuteVars.adInProgress) {
_tmuteVars.adInProgress = false;
_tmuteVars.adElapsedTime = undefined;
if (!_tmuteVars.wasMutedBeforeAd) unmuteMainPlayer();
actionHidePlayer(false);
} else if (!_tmuteVars.currentChannel || !window.location.pathname.startsWith("/" + _tmuteVars.currentChannel)) {
anticipatePreroll(true);
}
}
_tmuteVars.currentPage = window.location.pathname;
_tmuteVars.currentChannel = window.location.pathname.split("/")[1];
getAdNoticeElement();
}
// *** СТАРТ ***
currentSelector = _tmuteSelectors.hw;
_tmuteVars.autoCheck = setInterval(evaluateAdState, _tmuteVars.timerCheck);
setTimeout(initAdsOptions, 1000); // Ждем загрузки страницы
addHideCSS();
startDomObserver();
startFallback();
_tmuteVars.autoUpdate = setInterval(initUpdate, _tmuteVars.timerCheck);
restartCheckInterval();
// *** ЗАПУСК КНОПКИ С ПОВТОРАМИ ***
const initButton = () => {
initAdsOptions();
if (!_tmuteVars.optionsInitialized) {
setTimeout(initButton, 500);
}
};
initButton();
})();