Detects placeholder ads and refreshes the page, or for FFZ users, resets the player.
// ==UserScript==
// @name Twitch - Refresh on Advert
// @version 0.72
// @description Detects placeholder ads and refreshes the page, or for FFZ users, resets the player.
// @author CodingAndAlgorithm - videoPlayerObserver is based on code written by SimpleHacker
// @match https://www.twitch.tv/*
// @namespace https://greasyfork.org/users/701035
// ==/UserScript==
(function() {
'use strict';
let awaitingCompressorRestore = false;
let awaitingVolumeRestore = false;
let skippedFirstMutation = false;
let volumeHolder = 0.5;
let adTimestamp = null;
let overlay = null;
window.onload = function() {
let player = getVideoPlayer();
if(player) {
videoPlayerObserver.observe(player, {
childList: true,
subtree: true
});
}
logTime("Twitch - Refresh on Advert");
}
const videoPlayerObserver = new MutationObserver(function(mutations) {
mutations.forEach((mutation) => {
// Restore volume after a specific series of mutations that occur when the player is reset.
if(awaitingVolumeRestore && mutation.removedNodes.length == 1)
{
if(mutation.target.className == "tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-top-0 video-player__overlay"
&& mutation.previousSibling.className == "tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-top-0")
{
if(skippedFirstMutation)
{
restoreVolume();
}
else
{
skippedFirstMutation = true;
}
return;
}
}
// Wait for the compressor warning message to be removed before restoring.
if(awaitingCompressorRestore && mutation.removedNodes.length == 1)
{
var targetNode = mutation.removedNodes[0];
if(targetNode.nodeType == Node.TEXT_NODE)
{
if(targetNode.textContent == "Audio Compressor cannot be enabled when viewing Clips.")
{
restoreCompressorState();
return;
}
}
}
// Listen for adverts
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE)
{
let adBanner = node.querySelector('[data-test-selector="ad-banner-default-text"]');
if (adBanner)
{
// Let the ads run if blocking fails, an ad is better than the player constantly resfreshing for the duration of the ad.
// Twitch is routinely breaking the custom UBlockOrigin script.
let lastAdTime = adTimestamp;
adTimestamp = new Date();
if(lastAdTime != null && (adTimestamp - lastAdTime) / 1000 < 10)
{
logTime("UBlock Failed");
showOverlay();
setTimeout(hideOverlay, 15000);
return;
}
if (getFFZResetButton())
{
logTime("Advert Blocked")
// Hold our audio settings
// Player.volume has already been modified at this point, take the value from the volume slider instead.
volumeHolder = getVolumeSlider().value;
// Reset player
var dblClickEvent = document.createEvent ('MouseEvents');
dblClickEvent.initEvent ("dblclick", true, true);
getFFZResetButton().dispatchEvent(dblClickEvent);
awaitingCompressorRestore = isFFZCompressorActive();
awaitingVolumeRestore = true;
skippedFirstMutation = false;
}
else
{
window.location.reload();
}
}
}
});
});
});
function restoreVolume()
{
// Restore player volume & slider position
getVideo().volume = volumeHolder;
getVolumeSlider().value = volumeHolder;
awaitingVolumeRestore = false;
console.log("Restored Volume: " + Math.round(volumeHolder * 100) + "%");
}
function restoreCompressorState()
{
getFFZCompressorButton().click();
awaitingCompressorRestore = false;
console.log("Restored Compressor");
}
function logTime(message) {
console.log(new Date().toLocaleTimeString() + ": "+ message);
}
// TODO: Add lazy loading for getElement functions.
function getFFZCompressorButton() {
return document.querySelector('[data-a-target="ffz-player-comp-button"]');
}
function getFFZResetButton() {
return document.querySelector('[data-a-target="ffz-player-reset-button"]');
}
function getVideoPlayer() {
return document.querySelector('[data-a-target="video-player"]');
}
function getVideo() {
return document.querySelector('video');
}
function getVolumeSlider() {
return document.querySelector('[data-a-target="player-volume-slider"]');
}
function isFFZCompressorActive() {
return document.getElementsByClassName("ffz-player-icon ffz-i-comp-on")[0] != null;
}
function getOverlayParent() {
return document.getElementsByClassName("tw-absolute tw-bottom-0 tw-left-0 tw-right-0 tw-top-0 video-player__overlay")[0];
}
function showOverlay() {
if(overlay == null)
{
overlay = document.createElement("div");
overlay.style.position = "absolute";
overlay.style.left = "0px"
overlay.style.top = "50%"
overlay.style.height = "140px";
overlay.style.margin = "-70px 0 0 0"
overlay.style.width = "100%";
overlay.style.background = "#000000cc";
var message = document.createElement("h4");
message.innerHTML = "Twitch - Refresh on Advert depends on u/thesbros UBlock script, which Twitch is routinely bypassing.<br>This script will resume normal functionality once UBlock is up and running again.<br>Stay up to date by purging & updating your UBlock filter list regularly.";
message.style.padding = "30px 60px"
message.style.pointerEvents = "none"
overlay.appendChild(message);
getOverlayParent().appendChild(overlay);
}
overlay.style.display = "block";
}
function hideOverlay() {
if(overlay)
{
overlay.style.display = "none";
}
}
})();