Skip to content

Instantly share code, notes, and snippets.

@pixeltris
Last active April 7, 2021 19:34
Show Gist options
  • Save pixeltris/ee77c8338d7ebe1b9973dfcc973409d8 to your computer and use it in GitHub Desktop.
Save pixeltris/ee77c8338d7ebe1b9973dfcc973409d8 to your computer and use it in GitHub Desktop.
twitch-videoad.js application/javascript
(function() {
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
// Worker injection by instance01 (https://github.com/instance01/Twitch-HLS-AdBlock)
const oldWorker = window.Worker;
window.Worker = class Worker extends oldWorker {
constructor(twitchBlobUrl) {
var jsURL = getWasmWorkerUrl(twitchBlobUrl);
var version = jsURL.match(/wasmworker\.min\-(.*)\.js/)[1];
var functions = getFuncsForInjection();
var newBlobStr = `
var Module = {
WASM_BINARY_URL: '${jsURL.replace('.js', '.wasm')}',
WASM_CACHE_MODE: true
}
${ functions }
importScripts('${jsURL}');
`
super(URL.createObjectURL(new Blob([newBlobStr])));
var adDiv = null;
this.onmessage = function(e) {
if (e.data.key == 'UboShowAdBanner') {
if (adDiv == null) { adDiv = getAdDiv(); }
adDiv.style.display = 'block';
}
else if (e.data.key == 'UboHideAdBanner') {
if (adDiv == null) { adDiv = getAdDiv(); }
adDiv.style.display = 'none';
}
}
function getAdDiv() {
var msg = 'uBlock Origin is waiting for ads to finish...';
var playerRootDiv = document.querySelector(".video-player");
var adDiv = null;
if (playerRootDiv != null) {
adDiv = playerRootDiv.querySelector('.ubo-overlay');
if (adDiv == null) {
adDiv = document.createElement('div');
adDiv.className = 'ubo-overlay';
adDiv.innerHTML = '<div class="player-ad-notice" style="color: white; background-color: rgba(0, 0, 0, 0.8); position: absolute; top: 0px; left: 0px; padding: 10px;"><p>' + msg + '</p></div>';
adDiv.style.display = 'none';
playerRootDiv.appendChild(adDiv);
}
}
return adDiv;
}
}
}
function getWasmWorkerUrl (twitchBlobUrl) {
var req = new XMLHttpRequest();
req.open('GET', twitchBlobUrl, false);
req.send();
return req.responseText.split("'")[1];
}
function getFuncsForInjection () {
function stripAds (textStr) {
var haveAdTags = textStr.includes('#EXT-X-SCTE35-OUT') || textStr.includes('stitched-ad');
if (haveAdTags && !textStr.includes(',live')) {
textStr = "#EXTM3U\n#EXT-X-MEDIA-SEQUENCE:-1\n#EXT-X-TARGETDURATION:1\n#EXTINF:2.000,live\nhttps://localhost/twitch_url_which_will_always_fail.ts"
postMessage({key:'UboShowAdBanner'});
}
else {
postMessage({key:'UboHideAdBanner'});
if (haveAdTags) {
var trimStartIndex = -1;
var hasLiveSegment = false;
var lines = textStr.replace("\r", "").split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.startsWith("#EXT-X-PROGRAM-DATE-TIME")) {
trimStartIndex = i;
}
else if (line.startsWith("#EXTINF:")) {
if (!line.includes(",live") && trimStartIndex >= 0) {
for (var j = trimStartIndex; j < lines.length; j++) {
var isEnd = !lines[j].startsWith("#");
lines[j] = "";
if (isEnd) {
break;
}
}
}
}
}
textStr = lines.join("\n");
}
}
return textStr;
}
return `${stripAds.toString()}${fetchTs.toString()}fetchTs();`
}
function fetchTs() {
var realFetch = fetch;
fetch = async function(url, options) {
if (typeof url === 'string') {
if (url.endsWith('m3u8')) {
// Based on https://github.com/jpillora/xhook
return new Promise(function(resolve, reject) {
var processAfter = async function(response) {
var str = stripAds(await response.text());
var modifiedResponse = new Response(str);
resolve(modifiedResponse);
};
var send = function() {
return realFetch(url, options).then(function(response) {
processAfter(response);
})["catch"](function(err) {
console.log('fetch hook err ' + err);
reject(err);
});
};
send();
});
}
}
return realFetch.apply(this, arguments);
}
}
})();
@gwarser
Copy link

gwarser commented Oct 30, 2020

@pixeltris, I think (I cannot test extensively because of my limited connection) this causes Twitch to request m3u files in a loop, using more bandwidth than normal stream.

@pixeltris
Copy link
Author

pixeltris commented Oct 30, 2020

I've tested on chrome/firefox and I haven't noticed m3u8 spam. It will spam the m3u8 if you strip all segments which is why insert a fake sequence in such cases. This might break down at mid-roll ads as the sequence number that I use is -1. This could potentially cause the m3u8 spam (or most likely just break the stream). More advanced sequence number tracking is required in such scenarios but sequence modifications can introduce stuttering / loops.

The current duration for the fake sequence is set to 1 or 2 seconds I think before it requests another m3u8. If you want to increase that duration just modify the durations here #EXT-X-TARGETDURATION:1\n#EXTINF:2.000,live.

But yea improvements need to be made to this in general as I think midrolls just result in error code #1000 or something (likely due to the -1 squence). There's still a looping issue which occurs sometimes when first entering a stream too, I really need to inspect the m3u8 files when this happens to see what's going on, annoyingly it doesn't happen every time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment