Fix vaft buffering mitigation triggering on vods
- Fix bug where ad blocking banner would continue to show when navigating to vods / clips
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name TwitchAdSolutions (video-swap-new)
|
||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||
// @version 1.49
|
||||
// @version 1.50
|
||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js
|
||||
// @description Multiple solutions for blocking Twitch ads (video-swap-new)
|
||||
@@ -13,7 +13,7 @@
|
||||
// ==/UserScript==
|
||||
(function() {
|
||||
'use strict';
|
||||
const ourTwitchAdSolutionsVersion = 17;// Used to prevent conflicts with outdated versions of the scripts
|
||||
const ourTwitchAdSolutionsVersion = 18;// Used to prevent conflicts with outdated versions of the scripts
|
||||
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
|
||||
console.log("skipping video-swap-new as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
|
||||
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
||||
@@ -41,8 +41,8 @@
|
||||
scope.AdSegmentCache = new Map();
|
||||
scope.AllSegmentsAreAdSegments = false;
|
||||
}
|
||||
let twitchPlayerAndState = null;
|
||||
let localStorageHookFailed = false;
|
||||
let adBlockDiv = null;
|
||||
const twitchWorkers = [];
|
||||
const workerStringConflicts = [
|
||||
'twitch',
|
||||
@@ -128,6 +128,7 @@
|
||||
${getServerTimeFromM3u8.toString()}
|
||||
${replaceServerTimeInM3u8.toString()}
|
||||
${getStreamUrlForResolution.toString()}
|
||||
${updateAdblockBannerForStream.toString()}
|
||||
const workerString = getWasmWorkerJs('${twitchBlobUrl.replaceAll("'", "%27")}');
|
||||
declareOptions(self);
|
||||
gql_device_id = ${gql_device_id ? "'" + gql_device_id + "'" : null};
|
||||
@@ -171,33 +172,8 @@
|
||||
super(URL.createObjectURL(new Blob([newBlobStr])), options);
|
||||
twitchWorkers.push(this);
|
||||
this.addEventListener('message', (e) => {
|
||||
// NOTE: Removed adDiv caching as '.video-player' can change between streams?
|
||||
if (e.data.key == 'UboShowAdBanner' || e.data.key == 'UpdateAdBlockBannerStripping') {
|
||||
if (adBlockDiv == null) {
|
||||
adBlockDiv = getAdDiv();
|
||||
}
|
||||
if (adBlockDiv != null) {
|
||||
if (e.data.key == 'UpdateAdBlockBannerStripping') {
|
||||
const strippingAdsSuffix = ' (stripping)';
|
||||
if (adBlockDiv.P.textContent.endsWith(strippingAdsSuffix)) {
|
||||
if (!e.data.isStrippingAdSegments) {
|
||||
adBlockDiv.P.textContent = adBlockDiv.P.textContent.substring(0, adBlockDiv.P.textContent.length - strippingAdsSuffix.length);
|
||||
}
|
||||
} else if (e.data.isStrippingAdSegments) {
|
||||
adBlockDiv.P.textContent += strippingAdsSuffix;
|
||||
}
|
||||
} else {
|
||||
adBlockDiv.P.textContent = 'Blocking' + (e.data.isMidroll ? ' midroll' : '') + ' ads';
|
||||
adBlockDiv.style.display = 'block';
|
||||
}
|
||||
}
|
||||
} else if (e.data.key == 'UboHideAdBanner') {
|
||||
if (adBlockDiv == null) {
|
||||
adBlockDiv = getAdDiv();
|
||||
}
|
||||
if (adBlockDiv != null) {
|
||||
adBlockDiv.style.display = 'none';
|
||||
}
|
||||
if (e.data.key == 'UboUpdateAdBanner') {
|
||||
updateAdblockBanner(e.data);
|
||||
} else if (e.data.key == 'UboReloadPlayer') {
|
||||
reloadTwitchPlayer(false);
|
||||
} else if (e.data.key == 'UboPauseResumePlayer') {
|
||||
@@ -214,22 +190,6 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
function getAdDiv() {
|
||||
const playerRootDiv = document.querySelector('.video-player');
|
||||
let adDiv = null;
|
||||
if (playerRootDiv != null) {
|
||||
adDiv = playerRootDiv.querySelector('.ubo-overlay');
|
||||
if (adDiv == null) {
|
||||
adDiv = document.createElement('div');
|
||||
adDiv.className = 'ubo-overlay';
|
||||
adDiv.innerHTML = '<div class="player-ad-notice" style="color: white; background-color: rgba(0, 0, 0, 0.8); position: absolute; top: 0px; left: 0px; padding: 5px;"><p></p></div>';
|
||||
adDiv.style.display = 'none';
|
||||
adDiv.P = adDiv.querySelector('p');
|
||||
playerRootDiv.appendChild(adDiv);
|
||||
}
|
||||
}
|
||||
return adDiv;
|
||||
}
|
||||
}
|
||||
}
|
||||
let workerInstance = reinsertWorkers(newWorker, reinsert);
|
||||
@@ -273,6 +233,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function updateAdblockBannerForStream(streamInfo) {
|
||||
const isShowingAd = !!streamInfo.BackupEncodings;
|
||||
if (!isShowingAd && (streamInfo.IsStrippingAdSegments || streamInfo.NumStrippedAdSegments > 0)) {
|
||||
streamInfo.IsStrippingAdSegments = false;
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
}
|
||||
postMessage({
|
||||
key: 'UboUpdateAdBanner',
|
||||
isMidroll: streamInfo.IsMidroll,
|
||||
hasAds: isShowingAd,
|
||||
isStrippingAdSegments: streamInfo.IsStrippingAdSegments,
|
||||
numStrippedAdSegments: streamInfo.NumStrippedAdSegments
|
||||
});
|
||||
}
|
||||
async function onFoundAd(streamInfo, textStr, reloadPlayer, realFetch, url, resolutionInfo) {
|
||||
let result = textStr;
|
||||
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
||||
@@ -354,10 +328,10 @@
|
||||
if (reloadPlayer) {
|
||||
postMessage({key:'UboReloadPlayer'});
|
||||
}
|
||||
postMessage({key:'UboShowAdBanner',isMidroll:streamInfo.IsMidroll});
|
||||
updateAdblockBannerForStream(streamInfo);
|
||||
return result;
|
||||
}
|
||||
function stripAdSegments(textStr, stripAllSegments) {
|
||||
function stripAdSegments(textStr, stripAllSegments, streamInfo) {
|
||||
let hasStrippedAdSegments = false;
|
||||
const lines = textStr.replaceAll('\r', '').split('\n');
|
||||
const newAdUrl = 'https://twitch.tv';
|
||||
@@ -369,6 +343,9 @@
|
||||
.replaceAll(/(X-TV-TWITCH-AD-CLICK-TRACKING-URL=")(?:[^"]*)(")/g, `$1${newAdUrl}$2`);
|
||||
if (i < lines.length - 1 && line.startsWith('#EXTINF') && (!line.includes(',live') || stripAllSegments || AllSegmentsAreAdSegments)) {
|
||||
const segmentUrl = lines[i + 1];
|
||||
if (!AdSegmentCache.has(segmentUrl)) {
|
||||
streamInfo.NumStrippedAdSegments++;
|
||||
}
|
||||
AdSegmentCache.set(segmentUrl, Date.now());
|
||||
hasStrippedAdSegments = true;
|
||||
}
|
||||
@@ -383,16 +360,15 @@
|
||||
lines[i] = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
}
|
||||
streamInfo.IsStrippingAdSegments = hasStrippedAdSegments;
|
||||
AdSegmentCache.forEach((key, value, map) => {
|
||||
if (value < Date.now() - 120000) {
|
||||
map.delete(key);
|
||||
}
|
||||
});
|
||||
postMessage({
|
||||
key: 'UpdateAdBlockBannerStripping',
|
||||
isStrippingAdSegments: hasStrippedAdSegments
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
async function processM3U8(url, textStr, realFetch) {
|
||||
@@ -417,7 +393,6 @@
|
||||
streamInfo.BackupEncodings = null;
|
||||
streamInfo.BackupEncodingsStatus.clear();
|
||||
streamInfo.BackupEncodingsPlayerTypeIndex = -1;
|
||||
postMessage({key:'UboHideAdBanner'});
|
||||
postMessage({key:'UboReloadPlayer'});
|
||||
} else if (!streamM3u8.includes('"MIDROLL"') && !streamM3u8.includes('"midroll"')) {
|
||||
const lines = streamM3u8.replaceAll('\r', '').split('\n');
|
||||
@@ -441,12 +416,11 @@
|
||||
}
|
||||
} else if (haveAdTags && !streamInfo.IsMovingOffBackupEncodings) {
|
||||
textStr = await onFoundAd(streamInfo, textStr, true, realFetch, url, currentResolution);
|
||||
} else {
|
||||
postMessage({key:'UboHideAdBanner'});
|
||||
}
|
||||
if (IsAdStrippingEnabled) {
|
||||
textStr = stripAdSegments(textStr);
|
||||
textStr = stripAdSegments(textStr, false, streamInfo);
|
||||
}
|
||||
updateAdblockBannerForStream(streamInfo);
|
||||
return textStr;
|
||||
}
|
||||
function hookWorkerFetch() {
|
||||
@@ -519,6 +493,8 @@
|
||||
BackupEncodingsPlayerTypeIndex: -1,
|
||||
IsMovingOffBackupEncodings: false,
|
||||
IsMidroll: false,
|
||||
IsStrippingAdSegments: false,
|
||||
NumStrippedAdSegments: 0,
|
||||
UseFallbackStream: false,
|
||||
ChannelName: channelName,
|
||||
UsherParams: (new URL(url)).search,
|
||||
@@ -722,18 +698,19 @@
|
||||
if (typeof init.headers['Authorization'] === 'string' && init.headers['Authorization'] !== AuthorizationHeader) {
|
||||
postTwitchWorkerMessage('UpdateAuthorizationHeader', AuthorizationHeader = init.headers['Authorization']);
|
||||
}
|
||||
if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && !init.body.includes('picture-by-picture') && !init.body.includes('frontpage')) {
|
||||
if (OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken')) {
|
||||
const targetPlayerType = 'embed';
|
||||
let replacedPlayerType = '';
|
||||
const newBody = JSON.parse(init.body);
|
||||
if (Array.isArray(newBody)) {
|
||||
for (let i = 0; i < newBody.length; i++) {
|
||||
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType !== OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody[i].variables.playerType;
|
||||
newBody[i].variables.playerType = OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType !== OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE) {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody.variables.playerType;
|
||||
newBody.variables.playerType = OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE;
|
||||
}
|
||||
@@ -764,10 +741,42 @@
|
||||
return realFetch.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
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
|
||||
function updateAdblockBanner(data) {
|
||||
const playerRootDiv = document.querySelector('.video-player');
|
||||
if (playerRootDiv != null) {
|
||||
let adBlockDiv = null;
|
||||
adBlockDiv = playerRootDiv.querySelector('.adblock-overlay');
|
||||
if (adBlockDiv == null) {
|
||||
adBlockDiv = document.createElement('div');
|
||||
adBlockDiv.className = 'adblock-overlay';
|
||||
adBlockDiv.innerHTML = '<div class="player-adblock-notice" style="color: white; background-color: rgba(0, 0, 0, 0.8); position: absolute; top: 0px; left: 0px; padding: 5px;"><p></p></div>';
|
||||
adBlockDiv.style.display = 'none';
|
||||
adBlockDiv.P = adBlockDiv.querySelector('p');
|
||||
playerRootDiv.appendChild(adBlockDiv);
|
||||
}
|
||||
if (adBlockDiv != null) {
|
||||
if (!twitchPlayerAndState?.player?.core || !twitchPlayerAndState?.state) {
|
||||
twitchPlayerAndState = getPlayerAndState();
|
||||
}
|
||||
const isLive = twitchPlayerAndState?.state?.props?.content?.type === 'live';
|
||||
adBlockDiv.P.textContent = 'Blocking' + (data.isMidroll ? ' midroll' : '') + ' ads' + (data.isStrippingAdSegments ? ' (stripping)' : '');// + (data.numStrippedAdSegments > 0 ? ` (${data.numStrippedAdSegments})` : '');
|
||||
adBlockDiv.style.display = data.hasAds && isLive ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
function monitorLiveStatus() {
|
||||
if (!twitchPlayerAndState?.player?.core || !twitchPlayerAndState?.state) {
|
||||
twitchPlayerAndState = getPlayerAndState();
|
||||
}
|
||||
const isLive = twitchPlayerAndState?.state?.props?.content?.type === 'live';
|
||||
if (!isLive) {
|
||||
updateAdblockBanner({
|
||||
hasAds: false
|
||||
});
|
||||
}
|
||||
setTimeout(monitorLiveStatus, 1000);
|
||||
}
|
||||
function getPlayerAndState() {
|
||||
function findReactNode(root, constraint) {
|
||||
if (root.stateNode && constraint(root.stateNode)) {
|
||||
return root.stateNode;
|
||||
@@ -788,7 +797,7 @@
|
||||
if (rootNode && rootNode._reactRootContainer && rootNode._reactRootContainer._internalRoot && rootNode._reactRootContainer._internalRoot.current) {
|
||||
reactRootNode = rootNode._reactRootContainer._internalRoot.current;
|
||||
}
|
||||
if (reactRootNode == null) {
|
||||
if (reactRootNode == null && rootNode != null) {
|
||||
const containerName = Object.keys(rootNode).find(x => x.startsWith('__reactContainer'));
|
||||
if (containerName != null) {
|
||||
reactRootNode = rootNode[containerName];
|
||||
@@ -798,12 +807,24 @@
|
||||
}
|
||||
const reactRootNode = findReactRootNode();
|
||||
if (!reactRootNode) {
|
||||
console.log('Could not find react root');
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
let player = findReactNode(reactRootNode, node => node.setPlayerActive && node.props && node.props.mediaPlayerInstance);
|
||||
player = player && player.props && player.props.mediaPlayerInstance ? player.props.mediaPlayerInstance : null;
|
||||
const playerState = findReactNode(reactRootNode, node => node.setSrc && node.setInitialPlaybackSettings);
|
||||
return {
|
||||
player: player,
|
||||
state: playerState
|
||||
};
|
||||
}
|
||||
function reloadTwitchPlayer(isPausePlay) {
|
||||
const playerAndState = getPlayerAndState();
|
||||
if (!playerAndState) {
|
||||
console.log('Could not find react root');
|
||||
return;
|
||||
}
|
||||
const player = playerAndState.player;
|
||||
const playerState = playerAndState.state;
|
||||
if (!player) {
|
||||
console.log('Could not find player');
|
||||
return;
|
||||
@@ -954,6 +975,7 @@
|
||||
declareOptions(window);
|
||||
hookWindowWorker();
|
||||
hookFetch();
|
||||
monitorLiveStatus();
|
||||
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
|
||||
onContentLoaded();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user