Fix buffering issue after ad segments

- Also switch to an MutationObserver model for mute-black / vide-swap #16
This commit is contained in:
pixeltris 2021-02-03 14:15:11 +00:00
parent a6928c9d04
commit 2424ab3cc1
16 changed files with 934 additions and 525 deletions

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @description Multiple solutions for blocking Twitch ads
// @author pixeltris
// @match *://*.twitch.tv/*
@ -58,8 +58,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -91,6 +89,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -130,6 +129,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -205,7 +207,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -261,20 +263,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -323,8 +347,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -373,33 +395,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -427,6 +428,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -538,6 +545,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -869,6 +877,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -902,9 +911,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -945,6 +967,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -972,11 +999,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (mute-black)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/mute-black/mute-black.user.js
// @description Multiple solutions for blocking Twitch ads (mute-black)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (notify-reload)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-reload/notify-reload.user.js
// @description Multiple solutions for blocking Twitch ads (notify-reload)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (notify-strip-reload)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip-reload/notify-strip-reload.user.js
// @description Multiple solutions for blocking Twitch ads (notify-strip-reload)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (notify-strip)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/notify-strip/notify-strip.user.js
// @description Multiple solutions for blocking Twitch ads (notify-strip)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (proxy-m3u8)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/proxy-m3u8/proxy-m3u8.user.js
// @description Multiple solutions for blocking Twitch ads (proxy-m3u8)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (strip)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/strip/strip.user.js
// @description Multiple solutions for blocking Twitch ads (strip)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -126,6 +126,10 @@ namespace TwitchAdUtils
{
modifiedOptions = true;
}
if (lineTrimmed.StartsWith("// @name "))
{
line = line += " (" + dirInfo.Name + ")";
}
if (lineTrimmed.StartsWith("// @description"))
{
string url = "https://github.com/pixeltris/TwitchAdSolutions/raw/master/" + dirInfo.Name + "/" + dirInfo.Name + suffixUserscript;

View File

@ -49,8 +49,6 @@ twitch-videoad.js application/javascript
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -82,6 +80,7 @@ twitch-videoad.js application/javascript
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -121,6 +120,9 @@ twitch-videoad.js application/javascript
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -196,7 +198,7 @@ twitch-videoad.js application/javascript
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -252,20 +254,42 @@ twitch-videoad.js application/javascript
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -314,8 +338,6 @@ twitch-videoad.js application/javascript
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -364,33 +386,12 @@ twitch-videoad.js application/javascript
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -418,6 +419,12 @@ twitch-videoad.js application/javascript
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -529,6 +536,7 @@ twitch-videoad.js application/javascript
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -860,6 +868,7 @@ twitch-videoad.js application/javascript
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -893,9 +902,22 @@ twitch-videoad.js application/javascript
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -936,6 +958,11 @@ twitch-videoad.js application/javascript
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -963,11 +990,11 @@ twitch-videoad.js application/javascript
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name TwitchAdSolutions
// @name TwitchAdSolutions (video-swap)
// @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 1.3
// @version 1.4
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap/video-swap.user.js
// @description Multiple solutions for blocking Twitch ads (video-swap)
@ -60,8 +60,6 @@
scope.StreamInfos = [];
scope.StreamInfosByUrl = [];
scope.CurrentChannelNameFromM3U8 = null;
scope.LastAdUrl = null;
scope.LastAdTime = 0;
// Need this in both scopes. Window scope needs to update this to worker scope.
scope.gql_device_id = null;
}
@ -93,6 +91,7 @@
${processM3U8.toString()}
${getSegmentInfos.toString()}
${getSegmentInfosLines.toString()}
${getFinalSegUrl.toString()}
${hookWorkerFetch.toString()}
${declareOptions.toString()}
${getAccessToken.toString()}
@ -132,6 +131,9 @@
else if (e.data.key == 'UboReloadPlayer') {
reloadTwitchPlayer();
}
else if (e.data.key == 'UboPauseResumePlayer') {
reloadTwitchPlayer(true);
}
}
function getAdDiv() {
var playerRootDiv = document.querySelector('.video-player');
@ -207,7 +209,7 @@
if (i >= 2 && lines[i - 2].startsWith('#EXT-X-PROGRAM-DATE-TIME')) {
segInfo.dateTimeLineIndex = i - 2;
segInfo.dateTimeLine = lines[i - 2];
segInfo.dateTime = new Date(lines[i - 2].split(':')[1]);
segInfo.dateTime = new Date(lines[i - 2].substr(lines[i - 2].indexOf(':')));
}
result.segs.push(segInfo);
}
@ -263,20 +265,42 @@
}
return result;
}
function getFinalSegUrl(lines) {
for (var i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith("http")) {
return lines[i];
}
}
return null;
}
async function processM3U8(url, textStr, realFetch) {
var haveAdTags = textStr.includes(AD_SIGNIFIER);
if (OPT_MODE_STRIP_AD_SEGMENTS) {
var si = StreamInfosByUrl[url];
if (si != null) {
si.BackupSeqNumber = -1;
var lines = textStr.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith('#EXT-X-MEDIA-SEQUENCE:')) {
var oldRealSeq = si.RealSeqNumber;
si.RealSeqNumber = parseInt(/#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(lines[i])[1]);
if (!haveAdTags && si.FakeSeqNumber > 0) {
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better)
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
/*// We have some sequencing issues... for now lets pause/play and stop modifying sequence.
// TODO: Improve sequencing (determine if the m3u8 urls have actually changed)
si.FakeSeqNumber = 0;
si.BackupSeqNumber = -1;
postMessage({key:'UboPauseResumePlayer'});*/
// We previously modified the sequence number, we need to keep doing so (alternatively pause/playing might work better
var finalSegUrl = getFinalSegUrl(lines);
if (finalSegUrl != si.FinalSegUrl) {
si.FinalSegUrl = finalSegUrl;
// TODO: Maybe only do the jump check if there was an ad recently? (within the last 5 m3u8 requests)
var jump = Math.max(0, si.RealSeqNumber - oldRealSeq);
if (jump <= 3) {
si.FakeSeqNumber += Math.max(0, si.RealSeqNumber - oldRealSeq);
} else if (jump > 0) {
si.FakeSeqNumber++;
}
}
lines[i] = '#EXT-X-MEDIA-SEQUENCE:' + si.FakeSeqNumber;
console.log('No ad, but modifying sequence realSeq:' + si.RealSeqNumber + ' fakeSeq:' + si.FakeSeqNumber);
}
@ -325,8 +349,6 @@
return textStr;
}
if (haveAdTags) {
LastAdUrl = url;
LastAdTime = Date.now();
var streamInfo = StreamInfosByUrl[url];
if (streamInfo == null) {
console.log('Unknown stream url ' + url);
@ -375,33 +397,12 @@
var lines = textStr.replace('\r', '').split('\n');
var newLines = [];
if (backupM3u8 != null) {
var seqMatch = /#EXT-X-MEDIA-SEQUENCE:([0-9]*)/.exec(backupM3u8);
if (seqMatch != null) {
var oldBackupSeqNumber = streamInfo.BackupSeqNumber;
streamInfo.BackupSeqNumber = Math.max(0, parseInt(seqMatch[1]));
if (streamInfo.RealSeqNumber > 0) {
// We already have a real stream, this must be a midroll. We should therefore increment rather than just using backup directly.
// - If we don't do this then our sequence number will be broken and the stream will get stuck in a loading state.
if (streamInfo.FakeSeqNumber == 0) {
streamInfo.FakeSeqNumber = streamInfo.RealSeqNumber;
}
if (oldBackupSeqNumber == -1) {
// First backup sequence, assume +1
streamInfo.FakeSeqNumber++;
}
else {
streamInfo.FakeSeqNumber += Math.max(0, streamInfo.BackupSeqNumber - oldBackupSeqNumber);
}
} else {
streamInfo.FakeSeqNumber = streamInfo.BackupSeqNumber;
}
}
var backupLines = backupM3u8.replace('\r', '').split('\n');
var segInfos = getSegmentInfos(streamInfo, lines, backupLines);
newLines.push('#EXTM3U');
newLines.push('#EXT-X-VERSION:3');
newLines.push('#EXT-X-TARGETDURATION:' + segInfos.backup.targetDuration);
newLines.push('#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
newLines.push('');//#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber);
// The following will could cause issues when we stop stripping segments
//newLines.push('#EXT-X-TWITCH-ELAPSED-SECS:' + streamInfo.backup.elapsedSecs);
//newLines.push('#EXT-X-TWITCH-TOTAL-SECS:' + streamInfo.backup.totalSecs);
@ -429,6 +430,12 @@
}
}
}
var finalSegUrl = getFinalSegUrl(newLines);
if (finalSegUrl != streamInfo.FinalSegUrl) {
streamInfo.FinalSegUrl = finalSegUrl;
streamInfo.FakeSeqNumber++;// We might need something better than this for lager jumps in seq?
}
newLines[3] = '#EXT-X-MEDIA-SEQUENCE:' + streamInfo.FakeSeqNumber;
if (pushedLiveSegs > 0 || pushedBackupSegs > 0) {
console.log('liveSegs:' + pushedLiveSegs + ' backupSegs:' + pushedBackupSegs + ' prefetch:' + pushedPrefetchSegs + ' realSeq:' + streamInfo.RealSeqNumber + ' fakeSeq:' + streamInfo.FakeSeqNumber);
} else {
@ -540,6 +547,7 @@
streamInfo.RealSeqNumber = -1;
streamInfo.BackupSeqNumber = -1;
streamInfo.FakeSeqNumber = 0;
streamInfo.FinalSegUrl = null;
var lines = encodingsM3u8.replace('\r', '').split('\n');
for (var i = 0; i < lines.length; i++) {
if (!lines[i].startsWith('#') && lines[i].includes('.m3u8')) {
@ -871,6 +879,7 @@
}
}
function pollForAds() {
//console.log('pollForAds ' + new Date(Date.now()));
//check ad by looking for text banner
var adBanner = document.querySelectorAll("span.tw-c-text-overlay");
var foundAd = false;
@ -904,9 +913,22 @@
}
}
}
setTimeout(pollForAds,100);
//setTimeout(pollForAds,100);
}
function reloadTwitchPlayer() {
function pollForAdsObserver() {
pollForAds();
var vids = document.getElementsByClassName('video-player');
for (var i = 0; i < vids.length; i++) {
var observer = new MutationObserver(pollForAds);
observer.observe(vids[i], {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
}
}
function reloadTwitchPlayer(isPausePlay) {
// Taken from ttv-tools / ffz
// https://github.com/Nerixyz/ttv-tools/blob/master/src/context/twitch-player.ts
// https://github.com/FrankerFaceZ/FrankerFaceZ/blob/master/src/sites/twitch-twilight/modules/player.jsx
@ -947,6 +969,11 @@
if (player.paused) {
return;
}
if (isPausePlay) {
player.pause();
player.play();
return;
}
const sink = player.mediaSinkManager || (player.core ? player.core.mediaSinkManager : null);
if (sink && sink.video && sink.video._ffz_compressor) {
const video = sink.video;
@ -974,11 +1001,11 @@
var script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/hls.js@latest";
script.onload = function() {
pollForAds();
pollForAdsObserver();
}
document.head.appendChild(script);
} else {
pollForAds();
pollForAdsObserver();
}
}
hookFetch();