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:
@@ -27,7 +27,7 @@ Alternatively:
|
||||
|
||||
- vaft - [userscript](https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js) / [ublock](https://raw.githubusercontent.com/pixeltris/TwitchAdSolutions/master/vaft/vaft-ublock-origin.js) / [ublock (permalink)](https://raw.githubusercontent.com/pixeltris/TwitchAdSolutions/9cae451c04a9a94859da7de19c367b58afdd95bc/vaft/vaft-ublock-origin.js)
|
||||
- Attempts to get a clean stream as fast as it can
|
||||
- If it fails to get a clean stream it removes ad segements (no playback until ad-free stream is found)
|
||||
- If it fails to get a clean stream it removes ad segments (no playback until ad-free stream is found)
|
||||
- video-swap-new - [userscript](https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js) / [ublock](https://raw.githubusercontent.com/pixeltris/TwitchAdSolutions/master/video-swap-new/video-swap-new-ublock-origin.js) / [ublock (permalink)](https://raw.githubusercontent.com/pixeltris/TwitchAdSolutions/9cae451c04a9a94859da7de19c367b58afdd95bc/video-swap-new/video-swap-new-ublock-origin.js)
|
||||
- Attempts to get a clean stream
|
||||
- If it fails to get a clean stream it removes ad segments (no playback until ad-free stream is found)
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
- Mute ads and optionally hide them (tweak the settings at the top of the script).
|
||||
- `vaft` - [userscript](https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js)
|
||||
- Attempts to get a clean stream as fast as it can.
|
||||
- If it fails to get a clean stream it removes ad segements (no playback until ad-free stream is found).
|
||||
- If it fails to get a clean stream it removes ad segments (no playback until ad-free stream is found).
|
||||
- `video-swap-new` - [userscript](https://github.com/pixeltris/TwitchAdSolutions/raw/master/video-swap-new/video-swap-new.user.js)
|
||||
- Attempts to get a clean stream (often low resolution).
|
||||
- If it fails to get a clean stream it removes ad segments (no playback until ad-free stream is found).
|
||||
|
||||
+98
-63
@@ -2,7 +2,7 @@ twitch-videoad.js text/javascript
|
||||
(function() {
|
||||
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
||||
'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 vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
|
||||
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
||||
@@ -19,7 +19,7 @@ twitch-videoad.js text/javascript
|
||||
'picture-by-picture-CACHED'//360p (-CACHED is an internal suffix and is removed)
|
||||
];
|
||||
scope.FallbackPlayerType = 'embed';
|
||||
scope.ForceAccessTokenPlayerType = 'site';
|
||||
scope.ForceAccessTokenPlayerType = 'site';// Replaces 'embed' player type with 'site' (to reduce prerolls when on embeded websites)
|
||||
scope.SkipPlayerReloadOnHevc = false;// If true this will skip player reload on streams which have 2k/4k quality (if you enable this and you use the 2k/4k quality setting you'll get error #4000 / #3000 / spinning wheel on chrome based browsers)
|
||||
scope.AlwaysReloadPlayerOnAd = false;// Always pause/play when entering/leaving ads
|
||||
scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play
|
||||
@@ -49,7 +49,6 @@ twitch-videoad.js text/javascript
|
||||
let isActivelyStrippingAds = false;
|
||||
let localStorageHookFailed = false;
|
||||
const twitchWorkers = [];
|
||||
let adBlockDiv = null;
|
||||
const workerStringConflicts = [
|
||||
'twitch',
|
||||
'isVariantA'// TwitchNoSub
|
||||
@@ -186,20 +185,11 @@ twitch-videoad.js text/javascript
|
||||
twitchWorkers.push(this);
|
||||
this.addEventListener('message', (e) => {
|
||||
if (e.data.key == 'UpdateAdBlockBanner') {
|
||||
if (adBlockDiv == null) {
|
||||
adBlockDiv = getAdBlockDiv();
|
||||
}
|
||||
if (adBlockDiv != null) {
|
||||
isActivelyStrippingAds = e.data.isStrippingAdSegments;
|
||||
adBlockDiv.P.textContent = 'Blocking' + (e.data.isMidroll ? ' midroll' : '') + ' ads' + (e.data.isStrippingAdSegments ? ' (stripping)' : '');
|
||||
adBlockDiv.style.display = e.data.hasAds ? 'block' : 'none';
|
||||
}
|
||||
updateAdblockBanner(e.data);
|
||||
} else if (e.data.key == 'PauseResumePlayer') {
|
||||
doTwitchPlayerTask(true, false, false);
|
||||
doTwitchPlayerTask(true, false);
|
||||
} else if (e.data.key == 'ReloadPlayer') {
|
||||
doTwitchPlayerTask(false, true, false);
|
||||
} else if (e.data.key == 'MonitorPlayerForBuffering') {
|
||||
doTwitchPlayerTask(false, false, true);
|
||||
doTwitchPlayerTask(false, true);
|
||||
}
|
||||
});
|
||||
this.addEventListener('message', async event => {
|
||||
@@ -212,23 +202,6 @@ twitch-videoad.js text/javascript
|
||||
});
|
||||
}
|
||||
});
|
||||
function getAdBlockDiv() {
|
||||
//To display a notification to the user, that an ad is being blocked.
|
||||
const playerRootDiv = document.querySelector('.video-player');
|
||||
let adBlockDiv = null;
|
||||
if (playerRootDiv != 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);
|
||||
}
|
||||
}
|
||||
return adBlockDiv;
|
||||
}
|
||||
}
|
||||
};
|
||||
let workerInstance = reinsertWorkers(newWorker, reinsert);
|
||||
@@ -303,13 +276,11 @@ twitch-videoad.js text/javascript
|
||||
const encodingsM3u8 = await response.text();
|
||||
const serverTime = getServerTimeFromM3u8(encodingsM3u8);
|
||||
let streamInfo = StreamInfos[channelName];
|
||||
let isNewStream = false;
|
||||
if (streamInfo != null && streamInfo.EncodingsM3U8 != null && (await realFetch(streamInfo.EncodingsM3U8.match(/^https:.*\.m3u8$/m)[0])).status !== 200) {
|
||||
// The cached encodings are dead (the stream probably restarted)
|
||||
streamInfo = null;
|
||||
}
|
||||
if (streamInfo == null || streamInfo.EncodingsM3U8 == null) {
|
||||
isNewStream = true;
|
||||
StreamInfos[channelName] = streamInfo = {
|
||||
ChannelName: channelName,
|
||||
IsShowingAd: false,
|
||||
@@ -324,7 +295,8 @@ twitch-videoad.js text/javascript
|
||||
BackupEncodingsM3U8Cache: [],
|
||||
ActiveBackupPlayerType: null,
|
||||
IsMidroll: false,
|
||||
IsStrippingAdSegments: false
|
||||
IsStrippingAdSegments: false,
|
||||
NumStrippedAdSegments: 0
|
||||
};
|
||||
const lines = encodingsM3u8.replaceAll('\r', '').split('\n');
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
@@ -372,12 +344,7 @@ twitch-videoad.js text/javascript
|
||||
}
|
||||
}
|
||||
}
|
||||
postMessage({
|
||||
key: 'MonitorPlayerForBuffering'
|
||||
});
|
||||
if (streamInfo.IsUsingModifiedM3U8 || isNewStream) {
|
||||
streamInfo.LastPlayerReload = Date.now();
|
||||
}
|
||||
resolve(new Response(replaceServerTimeInM3u8(streamInfo.IsUsingModifiedM3U8 ? streamInfo.ModifiedM3U8 : streamInfo.EncodingsM3U8, serverTime)));
|
||||
} else {
|
||||
resolve(response);
|
||||
@@ -423,6 +390,9 @@ twitch-videoad.js text/javascript
|
||||
.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;
|
||||
}
|
||||
@@ -437,6 +407,8 @@ twitch-videoad.js text/javascript
|
||||
lines[i] = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
}
|
||||
streamInfo.IsStrippingAdSegments = hasStrippedAdSegments;
|
||||
AdSegmentCache.forEach((key, value, map) => {
|
||||
@@ -489,6 +461,15 @@ twitch-videoad.js text/javascript
|
||||
const haveAdTags = textStr.includes(AdSignifier) || SimulatedAdsDepth > 0;
|
||||
if (haveAdTags) {
|
||||
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
||||
if (!streamInfo.IsShowingAd) {
|
||||
streamInfo.IsShowingAd = true;
|
||||
postMessage({
|
||||
key: 'UpdateAdBlockBanner',
|
||||
isMidroll: streamInfo.IsMidroll,
|
||||
hasAds: streamInfo.IsShowingAd,
|
||||
isStrippingAdSegments: false
|
||||
});
|
||||
}
|
||||
if (!streamInfo.IsMidroll) {
|
||||
const lines = textStr.replaceAll('\r', '').split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
@@ -509,9 +490,6 @@ twitch-videoad.js text/javascript
|
||||
console.log('Ads will leak due to missing resolution info for ' + url);
|
||||
return textStr;
|
||||
}
|
||||
if (!streamInfo.IsShowingAd) {
|
||||
streamInfo.IsShowingAd = true;
|
||||
}
|
||||
const isHevc = currentResolution.Codecs.startsWith('hev') || currentResolution.Codecs.startsWith('hvc');
|
||||
if (((isHevc && !SkipPlayerReloadOnHevc) || AlwaysReloadPlayerOnAd) && streamInfo.ModifiedM3U8 && !streamInfo.IsUsingModifiedM3U8) {
|
||||
streamInfo.IsUsingModifiedM3U8 = true;
|
||||
@@ -569,7 +547,12 @@ twitch-videoad.js text/javascript
|
||||
backupM3u8 = m3u8Text;
|
||||
break;
|
||||
}
|
||||
if (isDoingMinimalRequests || isFullyCachedPlayerType) {
|
||||
if (isFullyCachedPlayerType) {
|
||||
break;
|
||||
}
|
||||
if (isDoingMinimalRequests) {
|
||||
backupPlayerType = playerType;
|
||||
backupM3u8 = m3u8Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -602,6 +585,7 @@ twitch-videoad.js text/javascript
|
||||
console.log('Finished blocking ads');
|
||||
streamInfo.IsShowingAd = false;
|
||||
streamInfo.IsStrippingAdSegments = false;
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
streamInfo.ActiveBackupPlayerType = null;
|
||||
if (streamInfo.IsUsingModifiedM3U8 || ReloadPlayerAfterAd) {
|
||||
streamInfo.IsUsingModifiedM3U8 = false;
|
||||
@@ -619,7 +603,8 @@ twitch-videoad.js text/javascript
|
||||
key: 'UpdateAdBlockBanner',
|
||||
isMidroll: streamInfo.IsMidroll,
|
||||
hasAds: streamInfo.IsShowingAd,
|
||||
isStrippingAdSegments: streamInfo.IsStrippingAdSegments
|
||||
isStrippingAdSegments: streamInfo.IsStrippingAdSegments,
|
||||
numStrippedAdSegments: streamInfo.NumStrippedAdSegments
|
||||
});
|
||||
return textStr;
|
||||
}
|
||||
@@ -698,19 +683,24 @@ twitch-videoad.js text/javascript
|
||||
bufferedPosition: 0,
|
||||
bufferDuration: 0,
|
||||
numSame: 0,
|
||||
lastFixTime: 0
|
||||
lastFixTime: 0,
|
||||
isLive: true
|
||||
};
|
||||
function monitorPlayerBuffering() {
|
||||
if (playerForMonitoringBuffering) {
|
||||
try {
|
||||
const player = playerForMonitoringBuffering;
|
||||
if (!player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
|
||||
const player = playerForMonitoringBuffering.player;
|
||||
const state = playerForMonitoringBuffering.state;
|
||||
if (!player.core) {
|
||||
playerForMonitoringBuffering = null;
|
||||
} else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
|
||||
const position = player.core?.state?.position;
|
||||
const bufferedPosition = player.core?.state?.bufferedPosition;
|
||||
const bufferDuration = player.getBufferDuration();
|
||||
//console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition);
|
||||
// NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play
|
||||
if ((playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
|
||||
if (position > 0 &&
|
||||
(playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
|
||||
playerBufferState.bufferedPosition == bufferedPosition &&
|
||||
playerBufferState.bufferDuration >= bufferDuration &&
|
||||
(position != 0 || bufferedPosition != 0 || bufferDuration != 0)
|
||||
@@ -719,6 +709,9 @@ twitch-videoad.js text/javascript
|
||||
if (playerBufferState.numSame == PlayerBufferingSameStateCount) {
|
||||
console.log('Attempt to fix buffering position:' + playerBufferState.position + ' bufferedPosition:' + playerBufferState.bufferedPosition + ' bufferDuration:' + playerBufferState.bufferDuration);
|
||||
doTwitchPlayerTask(!PlayerBufferingDoPlayerReload, PlayerBufferingDoPlayerReload, false);
|
||||
const isPausePlay = !PlayerBufferingDoPlayerReload;
|
||||
const isReload = PlayerBufferingDoPlayerReload;
|
||||
doTwitchPlayerTask(isPausePlay, isReload);
|
||||
playerBufferState.lastFixTime = Date.now();
|
||||
}
|
||||
} else {
|
||||
@@ -733,9 +726,45 @@ twitch-videoad.js text/javascript
|
||||
playerForMonitoringBuffering = null;
|
||||
}
|
||||
}
|
||||
if (!playerForMonitoringBuffering) {
|
||||
const playerAndState = getPlayerAndState();
|
||||
if (playerAndState && playerAndState.player && playerAndState.state) {
|
||||
playerForMonitoringBuffering = {
|
||||
player: playerAndState.player,
|
||||
state: playerAndState.state
|
||||
};
|
||||
}
|
||||
}
|
||||
const isLive = playerForMonitoringBuffering?.state?.props?.content?.type === 'live';
|
||||
if (playerBufferState.isLive && !isLive) {
|
||||
updateAdblockBanner({
|
||||
hasAds: false
|
||||
});
|
||||
}
|
||||
playerBufferState.isLive = isLive;
|
||||
setTimeout(monitorPlayerBuffering, PlayerBufferingDelay);
|
||||
}
|
||||
function doTwitchPlayerTask(isPausePlay, isReload, monitorPlayerForBuffering) {
|
||||
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) {
|
||||
isActivelyStrippingAds = data.isStrippingAdSegments;
|
||||
adBlockDiv.P.textContent = 'Blocking' + (data.isMidroll ? ' midroll' : '') + ' ads' + (data.isStrippingAdSegments ? ' (stripping)' : '');// + (data.numStrippedAdSegments > 0 ? ` (${data.numStrippedAdSegments})` : '');
|
||||
adBlockDiv.style.display = data.hasAds && playerBufferState.isLive ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
function getPlayerAndState() {
|
||||
function findReactNode(root, constraint) {
|
||||
if (root.stateNode && constraint(root.stateNode)) {
|
||||
return root.stateNode;
|
||||
@@ -756,7 +785,7 @@ twitch-videoad.js text/javascript
|
||||
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];
|
||||
@@ -766,12 +795,24 @@ twitch-videoad.js text/javascript
|
||||
}
|
||||
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 doTwitchPlayerTask(isPausePlay, isReload) {
|
||||
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;
|
||||
@@ -780,16 +821,9 @@ twitch-videoad.js text/javascript
|
||||
console.log('Could not find player state');
|
||||
return;
|
||||
}
|
||||
if (!playerForMonitoringBuffering) {
|
||||
playerForMonitoringBuffering = player;
|
||||
}
|
||||
if (player.isPaused() || player.core?.paused) {
|
||||
return;
|
||||
}
|
||||
if (monitorPlayerForBuffering) {
|
||||
playerBufferState.lastFixTime = 0;
|
||||
return;
|
||||
}
|
||||
if (isPausePlay) {
|
||||
player.pause();
|
||||
player.play();
|
||||
@@ -889,18 +923,19 @@ twitch-videoad.js text/javascript
|
||||
if (typeof init.headers['Authorization'] === 'string' && init.headers['Authorization'] !== AuthorizationHeader) {
|
||||
postTwitchWorkerMessage('UpdateAuthorizationHeader', AuthorizationHeader = init.headers['Authorization']);
|
||||
}
|
||||
if (ForceAccessTokenPlayerType && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && !init.body.includes('picture-by-picture') && !init.body.includes('frontpage')) {
|
||||
if (ForceAccessTokenPlayerType && 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 !== ForceAccessTokenPlayerType) {
|
||||
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody[i].variables.playerType;
|
||||
newBody[i].variables.playerType = ForceAccessTokenPlayerType;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType !== ForceAccessTokenPlayerType) {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody.variables.playerType;
|
||||
newBody.variables.playerType = ForceAccessTokenPlayerType;
|
||||
}
|
||||
|
||||
+99
-64
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name TwitchAdSolutions (vaft)
|
||||
// @namespace https://github.com/pixeltris/TwitchAdSolutions
|
||||
// @version 30.0.0
|
||||
// @version 31.0.0
|
||||
// @description Multiple solutions for blocking Twitch ads (vaft)
|
||||
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
||||
// @downloadURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
|
||||
@@ -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 vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
|
||||
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
|
||||
@@ -30,7 +30,7 @@
|
||||
'picture-by-picture-CACHED'//360p (-CACHED is an internal suffix and is removed)
|
||||
];
|
||||
scope.FallbackPlayerType = 'embed';
|
||||
scope.ForceAccessTokenPlayerType = 'site';
|
||||
scope.ForceAccessTokenPlayerType = 'site';// Replaces 'embed' player type with 'site' (to reduce prerolls when on embeded websites)
|
||||
scope.SkipPlayerReloadOnHevc = false;// If true this will skip player reload on streams which have 2k/4k quality (if you enable this and you use the 2k/4k quality setting you'll get error #4000 / #3000 / spinning wheel on chrome based browsers)
|
||||
scope.AlwaysReloadPlayerOnAd = false;// Always pause/play when entering/leaving ads
|
||||
scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play
|
||||
@@ -60,7 +60,6 @@
|
||||
let isActivelyStrippingAds = false;
|
||||
let localStorageHookFailed = false;
|
||||
const twitchWorkers = [];
|
||||
let adBlockDiv = null;
|
||||
const workerStringConflicts = [
|
||||
'twitch',
|
||||
'isVariantA'// TwitchNoSub
|
||||
@@ -197,20 +196,11 @@
|
||||
twitchWorkers.push(this);
|
||||
this.addEventListener('message', (e) => {
|
||||
if (e.data.key == 'UpdateAdBlockBanner') {
|
||||
if (adBlockDiv == null) {
|
||||
adBlockDiv = getAdBlockDiv();
|
||||
}
|
||||
if (adBlockDiv != null) {
|
||||
isActivelyStrippingAds = e.data.isStrippingAdSegments;
|
||||
adBlockDiv.P.textContent = 'Blocking' + (e.data.isMidroll ? ' midroll' : '') + ' ads' + (e.data.isStrippingAdSegments ? ' (stripping)' : '');
|
||||
adBlockDiv.style.display = e.data.hasAds ? 'block' : 'none';
|
||||
}
|
||||
updateAdblockBanner(e.data);
|
||||
} else if (e.data.key == 'PauseResumePlayer') {
|
||||
doTwitchPlayerTask(true, false, false);
|
||||
doTwitchPlayerTask(true, false);
|
||||
} else if (e.data.key == 'ReloadPlayer') {
|
||||
doTwitchPlayerTask(false, true, false);
|
||||
} else if (e.data.key == 'MonitorPlayerForBuffering') {
|
||||
doTwitchPlayerTask(false, false, true);
|
||||
doTwitchPlayerTask(false, true);
|
||||
}
|
||||
});
|
||||
this.addEventListener('message', async event => {
|
||||
@@ -223,23 +213,6 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
function getAdBlockDiv() {
|
||||
//To display a notification to the user, that an ad is being blocked.
|
||||
const playerRootDiv = document.querySelector('.video-player');
|
||||
let adBlockDiv = null;
|
||||
if (playerRootDiv != 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);
|
||||
}
|
||||
}
|
||||
return adBlockDiv;
|
||||
}
|
||||
}
|
||||
};
|
||||
let workerInstance = reinsertWorkers(newWorker, reinsert);
|
||||
@@ -314,13 +287,11 @@
|
||||
const encodingsM3u8 = await response.text();
|
||||
const serverTime = getServerTimeFromM3u8(encodingsM3u8);
|
||||
let streamInfo = StreamInfos[channelName];
|
||||
let isNewStream = false;
|
||||
if (streamInfo != null && streamInfo.EncodingsM3U8 != null && (await realFetch(streamInfo.EncodingsM3U8.match(/^https:.*\.m3u8$/m)[0])).status !== 200) {
|
||||
// The cached encodings are dead (the stream probably restarted)
|
||||
streamInfo = null;
|
||||
}
|
||||
if (streamInfo == null || streamInfo.EncodingsM3U8 == null) {
|
||||
isNewStream = true;
|
||||
StreamInfos[channelName] = streamInfo = {
|
||||
ChannelName: channelName,
|
||||
IsShowingAd: false,
|
||||
@@ -335,7 +306,8 @@
|
||||
BackupEncodingsM3U8Cache: [],
|
||||
ActiveBackupPlayerType: null,
|
||||
IsMidroll: false,
|
||||
IsStrippingAdSegments: false
|
||||
IsStrippingAdSegments: false,
|
||||
NumStrippedAdSegments: 0
|
||||
};
|
||||
const lines = encodingsM3u8.replaceAll('\r', '').split('\n');
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
@@ -383,12 +355,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
postMessage({
|
||||
key: 'MonitorPlayerForBuffering'
|
||||
});
|
||||
if (streamInfo.IsUsingModifiedM3U8 || isNewStream) {
|
||||
streamInfo.LastPlayerReload = Date.now();
|
||||
}
|
||||
resolve(new Response(replaceServerTimeInM3u8(streamInfo.IsUsingModifiedM3U8 ? streamInfo.ModifiedM3U8 : streamInfo.EncodingsM3U8, serverTime)));
|
||||
} else {
|
||||
resolve(response);
|
||||
@@ -434,6 +401,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;
|
||||
}
|
||||
@@ -448,6 +418,8 @@
|
||||
lines[i] = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
}
|
||||
streamInfo.IsStrippingAdSegments = hasStrippedAdSegments;
|
||||
AdSegmentCache.forEach((key, value, map) => {
|
||||
@@ -500,6 +472,15 @@
|
||||
const haveAdTags = textStr.includes(AdSignifier) || SimulatedAdsDepth > 0;
|
||||
if (haveAdTags) {
|
||||
streamInfo.IsMidroll = textStr.includes('"MIDROLL"') || textStr.includes('"midroll"');
|
||||
if (!streamInfo.IsShowingAd) {
|
||||
streamInfo.IsShowingAd = true;
|
||||
postMessage({
|
||||
key: 'UpdateAdBlockBanner',
|
||||
isMidroll: streamInfo.IsMidroll,
|
||||
hasAds: streamInfo.IsShowingAd,
|
||||
isStrippingAdSegments: false
|
||||
});
|
||||
}
|
||||
if (!streamInfo.IsMidroll) {
|
||||
const lines = textStr.replaceAll('\r', '').split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
@@ -520,9 +501,6 @@
|
||||
console.log('Ads will leak due to missing resolution info for ' + url);
|
||||
return textStr;
|
||||
}
|
||||
if (!streamInfo.IsShowingAd) {
|
||||
streamInfo.IsShowingAd = true;
|
||||
}
|
||||
const isHevc = currentResolution.Codecs.startsWith('hev') || currentResolution.Codecs.startsWith('hvc');
|
||||
if (((isHevc && !SkipPlayerReloadOnHevc) || AlwaysReloadPlayerOnAd) && streamInfo.ModifiedM3U8 && !streamInfo.IsUsingModifiedM3U8) {
|
||||
streamInfo.IsUsingModifiedM3U8 = true;
|
||||
@@ -580,7 +558,12 @@
|
||||
backupM3u8 = m3u8Text;
|
||||
break;
|
||||
}
|
||||
if (isDoingMinimalRequests || isFullyCachedPlayerType) {
|
||||
if (isFullyCachedPlayerType) {
|
||||
break;
|
||||
}
|
||||
if (isDoingMinimalRequests) {
|
||||
backupPlayerType = playerType;
|
||||
backupM3u8 = m3u8Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -613,6 +596,7 @@
|
||||
console.log('Finished blocking ads');
|
||||
streamInfo.IsShowingAd = false;
|
||||
streamInfo.IsStrippingAdSegments = false;
|
||||
streamInfo.NumStrippedAdSegments = 0;
|
||||
streamInfo.ActiveBackupPlayerType = null;
|
||||
if (streamInfo.IsUsingModifiedM3U8 || ReloadPlayerAfterAd) {
|
||||
streamInfo.IsUsingModifiedM3U8 = false;
|
||||
@@ -630,7 +614,8 @@
|
||||
key: 'UpdateAdBlockBanner',
|
||||
isMidroll: streamInfo.IsMidroll,
|
||||
hasAds: streamInfo.IsShowingAd,
|
||||
isStrippingAdSegments: streamInfo.IsStrippingAdSegments
|
||||
isStrippingAdSegments: streamInfo.IsStrippingAdSegments,
|
||||
numStrippedAdSegments: streamInfo.NumStrippedAdSegments
|
||||
});
|
||||
return textStr;
|
||||
}
|
||||
@@ -709,19 +694,24 @@
|
||||
bufferedPosition: 0,
|
||||
bufferDuration: 0,
|
||||
numSame: 0,
|
||||
lastFixTime: 0
|
||||
lastFixTime: 0,
|
||||
isLive: true
|
||||
};
|
||||
function monitorPlayerBuffering() {
|
||||
if (playerForMonitoringBuffering) {
|
||||
try {
|
||||
const player = playerForMonitoringBuffering;
|
||||
if (!player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
|
||||
const player = playerForMonitoringBuffering.player;
|
||||
const state = playerForMonitoringBuffering.state;
|
||||
if (!player.core) {
|
||||
playerForMonitoringBuffering = null;
|
||||
} else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
|
||||
const position = player.core?.state?.position;
|
||||
const bufferedPosition = player.core?.state?.bufferedPosition;
|
||||
const bufferDuration = player.getBufferDuration();
|
||||
//console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition);
|
||||
// NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play
|
||||
if ((playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
|
||||
if (position > 0 &&
|
||||
(playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
|
||||
playerBufferState.bufferedPosition == bufferedPosition &&
|
||||
playerBufferState.bufferDuration >= bufferDuration &&
|
||||
(position != 0 || bufferedPosition != 0 || bufferDuration != 0)
|
||||
@@ -730,6 +720,9 @@
|
||||
if (playerBufferState.numSame == PlayerBufferingSameStateCount) {
|
||||
console.log('Attempt to fix buffering position:' + playerBufferState.position + ' bufferedPosition:' + playerBufferState.bufferedPosition + ' bufferDuration:' + playerBufferState.bufferDuration);
|
||||
doTwitchPlayerTask(!PlayerBufferingDoPlayerReload, PlayerBufferingDoPlayerReload, false);
|
||||
const isPausePlay = !PlayerBufferingDoPlayerReload;
|
||||
const isReload = PlayerBufferingDoPlayerReload;
|
||||
doTwitchPlayerTask(isPausePlay, isReload);
|
||||
playerBufferState.lastFixTime = Date.now();
|
||||
}
|
||||
} else {
|
||||
@@ -744,9 +737,45 @@
|
||||
playerForMonitoringBuffering = null;
|
||||
}
|
||||
}
|
||||
if (!playerForMonitoringBuffering) {
|
||||
const playerAndState = getPlayerAndState();
|
||||
if (playerAndState && playerAndState.player && playerAndState.state) {
|
||||
playerForMonitoringBuffering = {
|
||||
player: playerAndState.player,
|
||||
state: playerAndState.state
|
||||
};
|
||||
}
|
||||
}
|
||||
const isLive = playerForMonitoringBuffering?.state?.props?.content?.type === 'live';
|
||||
if (playerBufferState.isLive && !isLive) {
|
||||
updateAdblockBanner({
|
||||
hasAds: false
|
||||
});
|
||||
}
|
||||
playerBufferState.isLive = isLive;
|
||||
setTimeout(monitorPlayerBuffering, PlayerBufferingDelay);
|
||||
}
|
||||
function doTwitchPlayerTask(isPausePlay, isReload, monitorPlayerForBuffering) {
|
||||
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) {
|
||||
isActivelyStrippingAds = data.isStrippingAdSegments;
|
||||
adBlockDiv.P.textContent = 'Blocking' + (data.isMidroll ? ' midroll' : '') + ' ads' + (data.isStrippingAdSegments ? ' (stripping)' : '');// + (data.numStrippedAdSegments > 0 ? ` (${data.numStrippedAdSegments})` : '');
|
||||
adBlockDiv.style.display = data.hasAds && playerBufferState.isLive ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
function getPlayerAndState() {
|
||||
function findReactNode(root, constraint) {
|
||||
if (root.stateNode && constraint(root.stateNode)) {
|
||||
return root.stateNode;
|
||||
@@ -767,7 +796,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];
|
||||
@@ -777,12 +806,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 doTwitchPlayerTask(isPausePlay, isReload) {
|
||||
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;
|
||||
@@ -791,16 +832,9 @@
|
||||
console.log('Could not find player state');
|
||||
return;
|
||||
}
|
||||
if (!playerForMonitoringBuffering) {
|
||||
playerForMonitoringBuffering = player;
|
||||
}
|
||||
if (player.isPaused() || player.core?.paused) {
|
||||
return;
|
||||
}
|
||||
if (monitorPlayerForBuffering) {
|
||||
playerBufferState.lastFixTime = 0;
|
||||
return;
|
||||
}
|
||||
if (isPausePlay) {
|
||||
player.pause();
|
||||
player.play();
|
||||
@@ -900,18 +934,19 @@
|
||||
if (typeof init.headers['Authorization'] === 'string' && init.headers['Authorization'] !== AuthorizationHeader) {
|
||||
postTwitchWorkerMessage('UpdateAuthorizationHeader', AuthorizationHeader = init.headers['Authorization']);
|
||||
}
|
||||
if (ForceAccessTokenPlayerType && typeof init.body === 'string' && init.body.includes('PlaybackAccessToken') && !init.body.includes('picture-by-picture') && !init.body.includes('frontpage')) {
|
||||
if (ForceAccessTokenPlayerType && 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 !== ForceAccessTokenPlayerType) {
|
||||
if (newBody[i]?.variables?.playerType && newBody[i]?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody[i].variables.playerType;
|
||||
newBody[i].variables.playerType = ForceAccessTokenPlayerType;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType !== ForceAccessTokenPlayerType) {
|
||||
if (newBody?.variables?.playerType && newBody?.variables?.playerType === targetPlayerType) {
|
||||
replacedPlayerType = newBody.variables.playerType;
|
||||
newBody.variables.playerType = ForceAccessTokenPlayerType;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
twitch-videoad.js text/javascript
|
||||
(function() {
|
||||
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
|
||||
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;
|
||||
@@ -29,8 +29,8 @@ twitch-videoad.js text/javascript
|
||||
scope.AdSegmentCache = new Map();
|
||||
scope.AllSegmentsAreAdSegments = false;
|
||||
}
|
||||
let twitchPlayerAndState = null;
|
||||
let localStorageHookFailed = false;
|
||||
let adBlockDiv = null;
|
||||
const twitchWorkers = [];
|
||||
const workerStringConflicts = [
|
||||
'twitch',
|
||||
@@ -116,6 +116,7 @@ twitch-videoad.js text/javascript
|
||||
${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};
|
||||
@@ -159,33 +160,8 @@ twitch-videoad.js text/javascript
|
||||
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') {
|
||||
@@ -202,22 +178,6 @@ twitch-videoad.js text/javascript
|
||||
});
|
||||
}
|
||||
});
|
||||
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);
|
||||
@@ -261,6 +221,20 @@ twitch-videoad.js text/javascript
|
||||
}
|
||||
}
|
||||
}
|
||||
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"');
|
||||
@@ -342,10 +316,10 @@ twitch-videoad.js text/javascript
|
||||
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';
|
||||
@@ -357,6 +331,9 @@ twitch-videoad.js text/javascript
|
||||
.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;
|
||||
}
|
||||
@@ -371,16 +348,15 @@ twitch-videoad.js text/javascript
|
||||
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) {
|
||||
@@ -405,7 +381,6 @@ twitch-videoad.js text/javascript
|
||||
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');
|
||||
@@ -429,12 +404,11 @@ twitch-videoad.js text/javascript
|
||||
}
|
||||
} 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() {
|
||||
@@ -507,6 +481,8 @@ twitch-videoad.js text/javascript
|
||||
BackupEncodingsPlayerTypeIndex: -1,
|
||||
IsMovingOffBackupEncodings: false,
|
||||
IsMidroll: false,
|
||||
IsStrippingAdSegments: false,
|
||||
NumStrippedAdSegments: 0,
|
||||
UseFallbackStream: false,
|
||||
ChannelName: channelName,
|
||||
UsherParams: (new URL(url)).search,
|
||||
@@ -710,18 +686,19 @@ twitch-videoad.js text/javascript
|
||||
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;
|
||||
}
|
||||
@@ -752,10 +729,42 @@ twitch-videoad.js text/javascript
|
||||
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;
|
||||
@@ -776,7 +785,7 @@ twitch-videoad.js text/javascript
|
||||
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];
|
||||
@@ -786,12 +795,24 @@ twitch-videoad.js text/javascript
|
||||
}
|
||||
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;
|
||||
@@ -942,6 +963,7 @@ twitch-videoad.js text/javascript
|
||||
declareOptions(window);
|
||||
hookWindowWorker();
|
||||
hookFetch();
|
||||
monitorLiveStatus();
|
||||
if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
|
||||
onContentLoaded();
|
||||
} else {
|
||||
|
||||
@@ -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