您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically mutes the Twitch player when an advertisement started and unmute it once finished. You can also hide ads by setting disableDisplay to true.
当前为
// ==UserScript== // @name Twitch - Mute ads and optionally hide them // @namespace TWITCHADS // @description Automatically mutes the Twitch player when an advertisement started and unmute it once finished. You can also hide ads by setting disableDisplay to true. // @include https://www.twitch.tv/* // @include https://twitch.tv/* // @version 1.144 // @license MIT // @author Harest // @grant none // ==/UserScript== (function() { var _tmuteVars = { "timerCheck": 1000, // Checking rate of ad in progress (in ms ; EDITABLE) "playerMuted": false, // Player muted or not (due to ad in progress) "adsDisplayed": 0, // Number of ads displayed "disableDisplay": false, // Disable the player display during an ad (true = yes, false = no (default) ; EDITABLE) "alreadyMuted": false, // Used to check if the player is muted at the start of an ad "adElapsedTime": undefined, // Used to check if Twitch forgot to remove the ad notice "adUnlockAt": 210, // Unlock the player if this amount of seconds elapsed during an ad (EDITABLE) "adMinTime": 7, // Minimum amount of seconds the player will be muted/hidden since an ad started (EDITABLE) "squadPage": false, // Either the current page is a squad page or not "playerIdAds": 0, // Player ID where ads may be displayed (default 0, varying on squads page) "displayingOptions": false, // Either ads options are currently displayed or not "highwindPlayer": undefined, // If you've the Highwind Player or not "classFallback": false // If we're in a browser without the main ad notice class }; // Selectors for the old player and the highwind one var _tmuteSelectors = { "old": { "player": "player-video", // Player class "playerVideo": ".player-video", // Player video selector "muteButton": ".player-button--volume", // (un)mute button selector "adNotice": "player-ad-notice", // Ad notice class "adNoticeFallback": "player-ad-notice", // Ad notice fallback class as the main one seems missing in at least Chrome "viewersCount": "channel-info-bar__viewers-wrapper", // Viewers count wrapper class "squadHeader": "squad-stream-top-bar__container", // Squad bar container class "squadPlayer": "multi-stream-player-layout__player-container", // Squad player class "squadPlayerMain": "multi-stream-player-layout__player-primary" // Squad primary player class }, "hw": { "player": "video-player__container", // Player class "playerVideo": ".video-player__container video", // Player video selector "muteButton": "button[data-a-target='player-mute-unmute-button']", // (un)mute button selector "adNotice": "Layout-sc-nxg1ff-0 FvFqf", // Ad notice class "adNoticeFallback": "FvFqf", // Ad notice fallback class as the main one seems missing in at least Chrome "viewersCount": "metadata-layout__support", // Viewers count wrapper class "squadHeader": "squad-stream-top-bar__container", // Squad bar container class "squadPlayer": "multi-stream-player-layout__player-container", // Squad player class "squadPlayerMain": "multi-stream-player-layout__player-primary" // Squad primary player class } }; // Current selector (either old or highwind player, automatically set below) var currentSelector = undefined; // Check if there's an ad function checkAd() { // Check if you're watching a stream, useless to continue if not if (_tmuteVars.highwindPlayer === undefined) { var isOldPlayer = document.getElementsByClassName(_tmuteSelectors.old.player).length; var isHwPlayer = document.getElementsByClassName(_tmuteSelectors.hw.player).length; var isViewing = Boolean(isOldPlayer + isHwPlayer); if (isViewing === false) return; // We set the type of player currently used (old or highwind one) _tmuteVars.highwindPlayer = Boolean(isHwPlayer); currentSelector = (_tmuteVars.highwindPlayer === true) ? _tmuteSelectors.hw : _tmuteSelectors.old; console.log("You're currently using the " + ((_tmuteVars.highwindPlayer === true) ? "Highwind" : "old") + " player."); } else { var isViewing = Boolean(document.getElementsByClassName(currentSelector.player).length); if (isViewing === false) return; } // Initialize the ads options if necessary. var optionsInitialized = (document.getElementById("_tmads_options") === null) ? false : true; if (optionsInitialized === false) adsOptions("init"); var advert = document.getElementsByClassName(currentSelector.adNotice)[0]; if (_tmuteVars.adElapsedTime !== undefined) { _tmuteVars.adElapsedTime += _tmuteVars.timerCheck / 1000; if (_tmuteVars.adElapsedTime >= _tmuteVars.adUnlockAt && advert.childNodes[1] !== undefined) { for (var i = 0; i < advert.childElementCount; i++) { if (!advert.childNodes[i].classList.contains(currentSelector.adNotice)) advert.removeChild(advert.childNodes[i]); } console.log("Unlocking Twitch player as Twitch forgot to remove the ad notice after the ad(s)."); } } if ((advert.childElementCount > 1 && _tmuteVars.playerMuted === false) || (_tmuteVars.playerMuted === true && advert.childElementCount === 1)) { // Update at the start of an ad if the player is already muted or not if (advert.childElementCount > 1) { var muteButton = document.querySelectorAll(currentSelector.muteButton)[_tmuteVars.playerIdAds]; if (_tmuteVars.highwindPlayer === true) { _tmuteVars.alreadyMuted = Boolean(muteButton.getAttribute("aria-label") === "Unmute (m)"); } else { _tmuteVars.alreadyMuted = Boolean(muteButton.childNodes[0].className === "unmute-button"); } } // Keep the player muted/hidden for the minimum ad time set (Twitch started to remove the ad notice before the end of some ads) if (advert.childElementCount === 1 && _tmuteVars.adElapsedTime !== undefined && _tmuteVars.adElapsedTime < _tmuteVars.adMinTime) return; mutePlayer(); } } // (un)Mute Player function mutePlayer() { if (document.querySelectorAll(currentSelector.muteButton).length >= 1) { if (_tmuteVars.alreadyMuted === false) document.querySelectorAll(currentSelector.muteButton)[_tmuteVars.playerIdAds].click(); // If the player is already muted before an ad, we avoid to unmute it. _tmuteVars.playerMuted = !(_tmuteVars.playerMuted); if (_tmuteVars.playerMuted === true) { _tmuteVars.adsDisplayed++; _tmuteVars.adElapsedTime = 1; console.log("Ad #" + _tmuteVars.adsDisplayed + " detected. Player " + (_tmuteVars.alreadyMuted === true ? "already " : "") + "muted."); if (_tmuteVars.disableDisplay === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = "hidden"; } else { console.log("Ad #" + _tmuteVars.adsDisplayed + " finished (lasted " + _tmuteVars.adElapsedTime + "s)." + (_tmuteVars.alreadyMuted === true ? "" : " Player unmuted.")); _tmuteVars.adElapsedTime = undefined; if (_tmuteVars.disableDisplay === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = "visible"; } } else { console.log("No volume button found (class changed ?)."); } } // Manage ads options function adsOptions(changeType = "show") { switch(changeType) { // Manage player display during an ad (either hiding the ads or still showing them) case "display": _tmuteVars.disableDisplay = !(_tmuteVars.disableDisplay); // Update the player display if an ad is supposedly in progress if (_tmuteVars.playerMuted === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = (_tmuteVars.disableDisplay === true) ? "hidden" : "visible"; document.getElementById("_tmads_display").innerText = (_tmuteVars.disableDisplay === true ? "Show" : "Hide") + " player during ads"; break; // Force a player unlock if Twitch didn't remove the ad notice properly instead of waiting the auto unlock case "unlock": var advert = document.getElementsByClassName(currentSelector.adNotice)[0]; if (_tmuteVars.classFallback === false) advert = advert.parentNode; if (_tmuteVars.adElapsedTime === undefined && advert.childNodes[1] === undefined) { alert("There's no ad notice displayed. No unlock to do."); } else { // We set the elapsed time to the unlock timer to trigger it during the next check. _tmuteVars.adElapsedTime = _tmuteVars.adUnlockAt; console.log("Unlock requested."); } break; // Display the ads options button case "init": if (document.getElementsByClassName(currentSelector.viewersCount)[0] === undefined && document.getElementsByClassName(currentSelector.squadHeader)[0] === undefined) break; // Check ad notice class exists, otherwise we'll use the fallback class if (document.getElementsByClassName(currentSelector.adNotice)[0] === undefined) { _tmuteVars.classFallback = true; currentSelector.adNotice = currentSelector.adNoticeFallback; console.log("Main ad notice class not found, falling back on another one."); // If the fallback isn't found either, we stop the script as Twitch did further changes that require a script update if (document.getElementsByClassName(currentSelector.adNotice)[0] === undefined) { clearInterval(_tmuteVars.autoCheck); console.log("Script stopped. Fallback ad notice class not found either, Twitch changed something. Feel free to contact the author of the script."); } } // Append ads options and events related var optionsTemplate = document.createElement("div"); optionsTemplate.id = "_tmads_options-wrapper"; optionsTemplate.className = "tw-inline-flex"; optionsTemplate.style = "padding-top: 10px;"; optionsTemplate.innerHTML = ` <span id="_tmads_options" style="display: none;"> <button type="button" id="_tmads_unlock" style="padding: 0 2px 0 2px; margin-left: 2px; height: 16px; width: unset;" class="tw-interactive tw-button-icon tw-button-icon--hollow">Unlock player</button> <button type="button" id="_tmads_display" style="padding: 0 2px 0 2px; margin-left: 2px; height: 16px; width: unset;" class="tw-interactive tw-button-icon tw-button-icon--hollow">` + (_tmuteVars.disableDisplay === true ? "Show" : "Hide") + ` player during ads</button> </span> <button type="button" id="_tmads_showoptions" style="padding: 0 2px 0 2px; margin-left: 2px; height: 16px; width: unset;" class="tw-interactive tw-button-icon tw-button-icon--hollow">Ads Options</button>`; // Normal player page if (document.getElementsByClassName(currentSelector.viewersCount)[0] !== undefined) { _tmuteVars.squadPage = false; _tmuteVars.playerIdAds = 0; document.getElementsByClassName(currentSelector.viewersCount)[0].parentNode.childNodes[1].appendChild(optionsTemplate); // Squad page } else if (document.getElementsByClassName(currentSelector.squadHeader)[0] !== undefined) { _tmuteVars.squadPage = true; _tmuteVars.playerIdAds = 0; // Since the primary player is never at the same place, we've to find it. for (var i = 0; i < parseInt(document.querySelectorAll(currentSelector.playerVideo).length); i++) { if (document.getElementsByClassName(currentSelector.squadPlayer)[0].childNodes[i].classList.contains(currentSelector.squadPlayerMain)) { _tmuteVars.playerIdAds = i; break; } } document.getElementsByClassName(currentSelector.squadHeader)[0].appendChild(optionsTemplate); } document.getElementById("_tmads_showoptions").addEventListener("click", adsOptions, false); document.getElementById("_tmads_display").addEventListener("click", function() { adsOptions("display"); }, false); document.getElementById("_tmads_unlock").addEventListener("click", function() { adsOptions("unlock"); }, false); console.log("Ads options initialized."); break; // Display/Hide the ads options case "show": default: _tmuteVars.displayingOptions = !(_tmuteVars.displayingOptions); document.getElementById("_tmads_options").style.display = (_tmuteVars.displayingOptions === false) ? "none" : "inline-flex"; } } // Start the background check _tmuteVars.autoCheck = setInterval(checkAd, _tmuteVars.timerCheck); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址