Twitch - Mute ads and optionally hide them

Automatically mutes the Twitch player when an advertisement started and unmute it once finished. You can also hide ads by setting disableDisplay to true.

当前为 2020-11-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Twitch - Mute ads and optionally hide them
  3. // @namespace TWITCHADS
  4. // @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.
  5. // @include https://www.twitch.tv/*
  6. // @include https://twitch.tv/*
  7. // @version 1.13
  8. // @license MIT
  9. // @author Harest
  10. // @grant none
  11. // ==/UserScript==
  12. (function() {
  13. var _tmuteVars = { "timerCheck": 1000, // Checking rate of ad in progress (in ms ; EDITABLE)
  14. "playerMuted": false, // Player muted or not
  15. "adsDisplayed": 0, // Number of ads displayed
  16. "disableDisplay": false, // Disable the player display during an ad (true = yes, false = no (default) ; EDITABLE)
  17. "alreadyMuted": false, // Used to check if the player is muted at the start of an ad
  18. "adElapsedTime": undefined, // Used to check if Twitch forgot to remove the ad notice
  19. "adUnlockAt": 150, // Unlock the player if this amount of seconds elapsed during an ad (EDITABLE)
  20. "adMinTime": 15, // Minimum amount of seconds the player will be muted/hidden since an ad started (EDITABLE ; Recommended to really avoid any ad: 30 to 45)
  21. "squadPage": false, // Either the current page is a squad page or not
  22. "playerIdAds": 0, // Player ID where ads may be displayed (default 0, varying on squads page)
  23. "displayingOptions": false, // Either ads options are currently displayed or not
  24. "highwindPlayer": undefined // If you've the Highwind Player or not (automatically checked)
  25. };
  26. // Selectors for the old player and the highwind one
  27. var _tmuteSelectors = { "old": { "player": "player-video", // Player class
  28. "playerVideo": ".player-video", // Player video selector
  29. "muteButton": ".player-button--volume", // (un)mute button selector
  30. "adNotice": "player-ad-notice", // Ad notice class
  31. "viewersCount": "channel-info-bar__viewers-wrapper", // Viewers count wrapper class
  32. "squadHeader": "squad-stream-top-bar__container", // Squad bar container class
  33. "squadPlayer": "multi-stream-player-layout__player-container", // Squad player class
  34. "squadPlayerMain": "multi-stream-player-layout__player-primary" // Squad primary player class
  35. },
  36. "hw": { "player": "video-player__container", // Player class
  37. "playerVideo": ".video-player__container video", // Player video selector
  38. "muteButton": "button[data-a-target='player-mute-unmute-button']", // (un)mute button selector
  39. "adNotice": "tw-absolute tw-c-background-overlay tw-c-text-overlay tw-inline-block tw-left-0 tw-pd-1 tw-top-0", // Ad notice class
  40. "viewersCount": "tw-align-items-center tw-flex tw-flex-wrap tw-full-width tw-justify-content-end tw-mg-l-1 tw-mg-t-05", // Viewers count wrapper class
  41. "squadHeader": "squad-stream-top-bar__container", // Squad bar container class
  42. "squadPlayer": "multi-stream-player-layout__player-container", // Squad player class
  43. "squadPlayerMain": "multi-stream-player-layout__player-primary" // Squad primary player class
  44. }
  45. };
  46. // Current selector (either old or highwind player, automatically set below)
  47. var currentSelector = undefined;
  48. // Check if there's an ad
  49. function checkAd()
  50. {
  51. // Check if you're watching a stream, useless to continue if not
  52. if (_tmuteVars.highwindPlayer === undefined) {
  53. var isOldPlayer = document.getElementsByClassName(_tmuteSelectors.old.player).length;
  54. var isHwPlayer = document.getElementsByClassName(_tmuteSelectors.hw.player).length;
  55. var isViewing = Boolean(isOldPlayer + isHwPlayer);
  56. if (isViewing === false) return;
  57. // We set the type of player currently used (old or highwind one)
  58. _tmuteVars.highwindPlayer = Boolean(isHwPlayer);
  59. currentSelector = (_tmuteVars.highwindPlayer === true) ? _tmuteSelectors.hw : _tmuteSelectors.old;
  60. console.log("You're currently using the " + ((_tmuteVars.highwindPlayer === true) ? "Highwind" : "old") + " player.");
  61. } else {
  62. var isViewing = Boolean(document.getElementsByClassName(currentSelector.player).length);
  63. if (isViewing === false) return;
  64. }
  65. // Initialize the ads options if necessary.
  66. var optionsInitialized = (document.getElementById("_tmads_options") === null) ? false : true;
  67. if (optionsInitialized === false) adsOptions("init");
  68. var advert = document.getElementsByClassName(currentSelector.adNotice);
  69. if (_tmuteVars.adElapsedTime !== undefined)
  70. {
  71. _tmuteVars.adElapsedTime++;
  72. if (_tmuteVars.adElapsedTime >= _tmuteVars.adUnlockAt && advert[0] !== undefined)
  73. {
  74. advert[0].parentNode.removeChild(advert[0]);
  75. console.log("Unlocking Twitch player as Twitch forgot to remove the ad notice after the ad(s).");
  76. }
  77. }
  78. if ((advert.length >= 1 && _tmuteVars.playerMuted === false) || (_tmuteVars.playerMuted === true && advert.length === 0))
  79. {
  80. // Update at the start of an ad if the player is already muted or not
  81. if (advert.length >= 1) {
  82. var muteButton = document.querySelectorAll(currentSelector.muteButton)[_tmuteVars.playerIdAds];
  83. if (_tmuteVars.highwindPlayer === true) {
  84. _tmuteVars.alreadyMuted = Boolean(muteButton.getAttribute("aria-label") === "Unmute (m)");
  85. } else {
  86. _tmuteVars.alreadyMuted = Boolean(muteButton.childNodes[0].className === "unmute-button");
  87. }
  88. }
  89. // Keep the player muted/hidden for the minimum ad time set (Twitch started to remove the ad notice before the end of some ads)
  90. if (advert.length === 0 && _tmuteVars.adElapsedTime !== undefined && _tmuteVars.adElapsedTime < _tmuteVars.adMinTime) return;
  91.  
  92. mutePlayer();
  93. }
  94. }
  95.  
  96. // (un)Mute Player
  97. function mutePlayer()
  98. {
  99. if (document.querySelectorAll(currentSelector.muteButton).length >= 1)
  100. {
  101. 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.
  102. _tmuteVars.playerMuted = !(_tmuteVars.playerMuted);
  103.  
  104. if (_tmuteVars.playerMuted === true)
  105. {
  106. _tmuteVars.adsDisplayed++;
  107. _tmuteVars.adElapsedTime = 1;
  108. console.log("Ad #" + _tmuteVars.adsDisplayed + " detected. Player " + (_tmuteVars.alreadyMuted === true ? "already " : "") + "muted.");
  109. if (_tmuteVars.disableDisplay === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = "hidden";
  110. } else {
  111. console.log("Ad #" + _tmuteVars.adsDisplayed + " finished (lasted " + _tmuteVars.adElapsedTime + "s)." + (_tmuteVars.alreadyMuted === true ? "" : " Player unmuted."));
  112. _tmuteVars.adElapsedTime = undefined;
  113. if (_tmuteVars.disableDisplay === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = "visible";
  114. }
  115. } else {
  116. console.log("No volume button found (class changed ?).");
  117. }
  118. }
  119. // Manage ads options
  120. function adsOptions(changeType = "show")
  121. {
  122. switch(changeType) {
  123. // Manage player display during an ad (either hiding the ads or still showing them)
  124. case "display":
  125. _tmuteVars.disableDisplay = !(_tmuteVars.disableDisplay);
  126. // Update the player display if an ad is supposedly in progress
  127. if (_tmuteVars.playerMuted === true) document.querySelectorAll(currentSelector.playerVideo)[_tmuteVars.playerIdAds].style.visibility = (_tmuteVars.disableDisplay === true) ? "hidden" : "visible";
  128. document.getElementById("_tmads_display").innerText = (_tmuteVars.disableDisplay === true ? "Show" : "Hide") + " player during ads";
  129. break;
  130. // Force a player unlock if Twitch didn't remove the ad notice properly instead of waiting the auto unlock
  131. case "unlock":
  132. var advert = document.getElementsByClassName(currentSelector.adNotice);
  133. if (_tmuteVars.adElapsedTime === undefined && advert[0] === undefined)
  134. {
  135. alert("There's no ad notice displayed. No unlock to do.");
  136. } else {
  137. // We set the elapsed time to the unlock timer to trigger it during the next check.
  138. _tmuteVars.adElapsedTime = _tmuteVars.adUnlockAt;
  139. console.log("Unlock requested.");
  140. }
  141. break;
  142. // Display the ads options button
  143. case "init":
  144. if (document.getElementsByClassName(currentSelector.viewersCount)[0] === undefined && document.getElementsByClassName(currentSelector.squadHeader)[0] === undefined) break;
  145. // Append ads options and events related
  146. var optionsTemplate = document.createElement("div");
  147. optionsTemplate.id = "_tmads_options-wrapper";
  148. optionsTemplate.className = "tw-inline-flex";
  149. optionsTemplate.innerHTML = `
  150. <span id="_tmads_options" style="display: none;">
  151. <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>
  152. <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>
  153. </span>
  154. <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>`;
  155. // Normal player page
  156. if (document.getElementsByClassName(currentSelector.viewersCount)[0] !== undefined)
  157. {
  158. _tmuteVars.squadPage = false;
  159. _tmuteVars.playerIdAds = 0;
  160. document.getElementsByClassName(currentSelector.viewersCount)[0].childNodes[0].appendChild(optionsTemplate);
  161. // Squad page
  162. } else if (document.getElementsByClassName(currentSelector.squadHeader)[0] !== undefined)
  163. {
  164. _tmuteVars.squadPage = true;
  165. _tmuteVars.playerIdAds = 0;
  166. // Since the primary player is never at the same place, we've to find it.
  167. for (var i = 0; i < parseInt(document.querySelectorAll(currentSelector.playerVideo).length); i++)
  168. {
  169. if (document.getElementsByClassName(currentSelector.squadPlayer)[0].childNodes[i].classList.contains(currentSelector.squadPlayerMain))
  170. {
  171. _tmuteVars.playerIdAds = i;
  172. break;
  173. }
  174. }
  175. document.getElementsByClassName(currentSelector.squadHeader)[0].appendChild(optionsTemplate);
  176. }
  177. document.getElementById("_tmads_showoptions").addEventListener("click", adsOptions, false);
  178. document.getElementById("_tmads_display").addEventListener("click", function() { adsOptions("display"); }, false);
  179. document.getElementById("_tmads_unlock").addEventListener("click", function() { adsOptions("unlock"); }, false);
  180. console.log("Ads options initialized.");
  181. break;
  182. // Display/Hide the ads options
  183. case "show":
  184. default:
  185. _tmuteVars.displayingOptions = !(_tmuteVars.displayingOptions);
  186. document.getElementById("_tmads_options").style.display = (_tmuteVars.displayingOptions === false) ? "none" : "inline-flex";
  187. }
  188. }
  189. // Start the background check
  190. _tmuteVars.autoCheck = setInterval(checkAd, _tmuteVars.timerCheck);
  191. })();