Twitch Ad Bypass
(March 24, 2021)

Twitch is a live streaming platform which like a lot of websites, has plenty of ads, and unfortunately ad blockers like uBlock Origin haven't been working recently. A few months ago I made a simple 'ad muter' which automatically mutes and hides the video, so you end up looking at a black screen for 30 seconds instead of an ad. I've now improved it to become an 'ad bypass' which displays an embedded stream allowing you to continue watching the live stream while an ad plays in the background. It uses more bandwidth due to running the ad stream in the background but I think it's worth it because you dont see an ad, still get to watch the stream and theoretically streamers will still earn money for you watching the ad in the background.

If your only interested in the ad bypass you can follow these instructions to install the script to uBlock Origin.
Video Instructions
There's 2 versions of the script, one with mobile quality embedding to save bandwidth https://pastebin.com/raw/bWQ3yBWT and another with full quality https://pastebin.com/raw/Yk7pzN9q. Navigate to your uBlock Origin settings which can be found by clicking the uBlock Origin icon in the top right of your web browser and then the cog wheel icon. On the settings page you will see 'I am an Advanced User', click the cog wheel icon beside that to open advanced settings. Scroll down to the bottom of advanced settings to find 'userResourcesLocation' and add the script URL, you can separate multiple resource URLs with spaces. Finally leave advanced settings and go to 'My Filters' and add twitch.tv##+js(TtvAdBypass.js), now if it's activated you should see a console(F12) message like 'Loaded Twitch Ad Bypass...' on any Twitch page.



Detecting Ads
To begin we need to detect ads on Twitch from Javascript, if you watch an ad play you might notice it displays a dark gray bar on the top with a message like 'Catch SomeStreamer right after this ad break;'.


This gray bar is an HTML document element which we can detect in Javascript by searching for class names via document.getElementsByClassName or document.querySelector. The element class name can be found by right clicking on the element and selecting 'Inspect Element', it should open up the HTML debugging window where you can view HTML attributes like classes. I found the HTML element to have the class "span.tw-c-text-overlay", now running document.querySelector("span.tw-c-text-overlay") should return the text element if found or null if no ad is playing.

Below is the code for our ad detection loop, once the 'detectAds()' function has been called it will run periodically every 100 milliseconds to check for ads.
function detectAds() {
	//look for html element of banner by class name
	if (document.querySelector("span.tw-c-text-overlay")) {
		//found ad
	}

	//periodically check for ads
	setTimeout(detectAds,100);
}

//begin loop of checking for ads
detectAds();

To test our Javascript code copy and paste the above code in the console(F12) and run it by pressing enter.



Hiding and Muting Ads
Now inside our main detection loop we can apply our ad hiding and muting but to do that we need to find the ad videos. Twitch sometimes displays multiple ads including above chat so we need to find all videos on the page and hide them. We can find all the video elements using document.getElementsByTagName, running document.getElementsByTagName("video") should return an array of all video elements on the page. Once we have the video elements we can mute them by setting volume to 0 and hide them by applying a css filter.
//find ad videos
var adVideos = document.getElementsByTagName("video");

//hide and mute
for (var i = 0; i < adVideos.length; i++) {
	adVideos[i].volume = 0;
	adVideos[i].style.filter = "brightness(0%)";
}


In the process of muting and hiding all videos we also hide/mute the actual livestream video itself, so when ads are disabled we need to go through unmuting and unhiding the videos. We will keep track of hidden/muted videos in a 'disabledVideos' array and when ads are no longer detected move volume back to 1 and remove the css filter.
var disabledVideos = [];

function detectAds() {
	//look for html element of banner by class name
	if (document.querySelector("span.tw-c-text-overlay")) {
		//if found ad banner, find ad videos
	    var liveVid = document.getElementsByTagName("video");
	    for (var i = 0; i < liveVid.length; i++) {
	    	var vid = liveVid[i];
	    	//check if already hidden/muted
	    	if (disabledVideos.indexOf(vid) === -1) disabledVideos.push(vid);
	    }
	    //mute and hide ad videos
	    for (var i = 0; i < disabledVideos.length; i++) {
	        var vid = disabledVideos[i];
	        vid.volume = 0;
	        vid.style.filter = "brightness(0%)";
	    }
	} else {
		//no ad found, re enable any hidden videos
	    if (disabledVideos.length) {
	        for (var i = 0; i < disabledVideos.length; i++) {
	           	disabledVideos[i].volume = 1;
	            disabledVideos[i].style.filter = "";
	        }
	        disabledVideos.length = 0;
	    }
	}

	//periodically check for ads
	setTimeout(detectAds,100);
}

//begin loop of checking for ads
detectAds();



Using Embeds to Bypass Ads
Plenty of social media sites have sharing and embedding links, Twitch is no different. Look below a stream to find a 'Share' button, in the menu that pops up you can find code for embedding Twitch live streams. What were interested in is the iframe embed at the bottom.
<iframe src="https://player.twitch.tv/?channel=channelname&parent=www.example.com" frameborder="0" allowfullscreen="true" scrolling="no" height="378" width="620"></iframe>

That HTML code is all we need to display an embedded Twitch livestream, when an ad displays were going to put an embedded livestream over the ad so you can continue watching the stream.

We need to detect what stream is currently being watched and insert their channel name into the URL where 'channelname' is, you can find this at location.pathname. Were going to implement all the embedding in Javascript, so we need to create an iframe with document.createElement("iframe"). To display the iframe over top we need to fit the video rectangle exactly, to find the video rectangle were going to use document.querySelector("div.video-player__overlay") to find the video overlay and the rectangle with getBoundingClientRect. Finally apply CSS styling to display it as an overlay, set zIndex to a large value(1e5), set position to absolute and the left/right/width/height to the bounding rectangle from our video overlay.
//find streamer name
var sname = location.pathname;
if (sname.indexOf("/") === 0) {
	sname = sname.substring(1);

	//find video display rect
	var vid = document.querySelector("div.video-player__overlay");
	if (vid) {
		var rect = vid.getBoundingClientRect();

		//create overlay of embedded stream
		var ovl = document.createElement("iframe"),
			st = ovl.style;
		st.zIndex = 1e5;
		st.position = "absolute";
		st.left = (rect.x+window.scrollX)+"px";
		st.top = (rect.y+window.scrollY)+"px";
		st.width = rect.width+"px";
		st.height = rect.height+"px";
		ovl.src = "//player.twitch.tv/?channel="+sname+"&parent=twitch.tv";
		ovl.frameborder = 0;
		document.body.appendChild(ovl);
		embed = ovl;
	}
}



Putting It All Together
Below you can see the final code I settled on.
(function() {
console.log("Loaded Twitch Ad Bypass v1 by Ethan Alexander Shulman 2021");

var disabledVideos = [],
	embed = null;
function checkForAd() {
	try {
		//look for html element of banner announcing the ad
		var adBanner = document.querySelectorAll("span.tw-c-text-overlay"), foundAd = false;
		for (var i = 0; i < adBanner.length; i++) {
		    if (adBanner[i].attributes["data-test-selector"]) {
		        foundAd = true;
		        break;
		    }
		}
		if (foundAd) {
			//if found ad banner, find ad videos
		    var liveVid = document.getElementsByTagName("video");
		    for (var i = 0; i < liveVid.length; i++) {
		    	var vid = liveVid[i];
		    	if (disabledVideos.indexOf(vid) === -1) disabledVideos.push(vid);
		    }
		    //mute and hide ad videos
		    for (var i = 0; i < disabledVideos.length; i++) {
		        var vid = disabledVideos[i];
		        vid.volume = 1e-3;
		        vid.style.filter = "brightness(0%)";
		    }
		    
		    //display embed overlay
		    if (!embed) {
		    	//prevent infinite loop
		    	if (window.self === window.top) {
					//find streamer name
					var sname = window.location.pathname;
					if (sname.indexOf("/") === 0) {
						sname = sname.substring(1);
				
						//find video display rect
						var vid = document.querySelector("div.video-player__overlay");
						if (vid) {
							var rect = vid.getBoundingClientRect();
							
							//doesnt work in fullscreen so exit fullscreen if detected
							if (document.fullscreenElement) {
								document.exitFullscreen();
								rect = {x: 0, y: 0, width: window.innerWidth, height: window.innerHeight-150};
							}
						
							//create overlay of embedded stream
							var ovl = document.createElement("iframe"),
								st = ovl.style;
							st.zIndex = 1e5;
							st.position = "absolute";
							st.left = (rect.x+window.scrollX)+"px";
							st.top = (rect.y+window.scrollY)+"px";
							st.width = rect.width+"px";
							st.height = rect.height+"px";
							ovl.src = "//player.twitch.tv/?channel="+sname+"&parent=twitch.tv&quality=mobile";
							ovl.frameborder = 0;
							document.body.appendChild(ovl);
							embed = ovl;
						}
					}
		    	}
		    }
		} else {
			//no ad found, re enable any hidden videos
		    if (disabledVideos.length) {
		        for (var i = 0; i < disabledVideos.length; i++) {
		           	disabledVideos[i].volume = 1;
		            disabledVideos[i].style.filter = "";
		        }
		        disabledVideos.length = 0;
		    }
		    
		    //disable embed overlay
		    if (embed) {
				embed.remove();
				embed = null;
		    }
		}
    } catch (e) {}
    
    //repeatedly check for ads
    setTimeout(checkForAd,100);
}

//begin loop of checking for ads
checkForAd();
}());


If you are interested how to run the Javascript automatically as a uBlock Origin filter, check out my other blog post
uBlock Origin Scriptlet Injection Tutorial.



Thanks for Reading!
If you have any questions feel free to reach out to me on Twitter or Discord.

XALOEZ.com