Fix vaft streams starting in a paused state when opened in a new tab #492

This commit is contained in:
pixeltris
2026-02-28 11:27:48 +00:00
parent ddb2a0cb1f
commit f8f86706da
2 changed files with 71 additions and 23 deletions
+31 -7
View File
@@ -2,7 +2,7 @@ twitch-videoad.js text/javascript
(function() { (function() {
if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; } if ( /(^|\.)twitch\.tv$/.test(document.location.hostname) === false ) { return; }
'use strict'; 'use strict';
const ourTwitchAdSolutionsVersion = 23;// Used to prevent conflicts with outdated versions of the scripts const ourTwitchAdSolutionsVersion = 24;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -23,7 +23,7 @@ twitch-videoad.js text/javascript
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.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.AlwaysReloadPlayerOnAd = false;// Always pause/play when entering/leaving ads
scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play
scope.PlayerReloadMinimalRequestsTime = 1600; scope.PlayerReloadMinimalRequestsTime = 1500;
scope.PlayerReloadMinimalRequestsPlayerIndex = 2;//autoplay scope.PlayerReloadMinimalRequestsPlayerIndex = 2;//autoplay
scope.HasTriggeredPlayerReload = false; scope.HasTriggeredPlayerReload = false;
scope.StreamInfos = []; scope.StreamInfos = [];
@@ -39,7 +39,7 @@ twitch-videoad.js text/javascript
scope.PlayerBufferingSameStateCount = 3;// How many times of seeing the same player state until we trigger pause/play (it will only trigger it one time until the player state changes again) scope.PlayerBufferingSameStateCount = 3;// How many times of seeing the same player state until we trigger pause/play (it will only trigger it one time until the player state changes again)
scope.PlayerBufferingDangerZone = 1;// The buffering time left (in seconds) when we should ignore the players playback position in the player state check scope.PlayerBufferingDangerZone = 1;// The buffering time left (in seconds) when we should ignore the players playback position in the player state check
scope.PlayerBufferingDoPlayerReload = false;// If true this will do a player reload instead of pause/play (player reloading is better at fixing the playback issues but it takes slightly longer) scope.PlayerBufferingDoPlayerReload = false;// If true this will do a player reload instead of pause/play (player reloading is better at fixing the playback issues but it takes slightly longer)
scope.PlayerBufferingMinRepeatDelay = 12000;// Minimum delay (in milliseconds) between each pause/play (this is to avoid over pressing pause/play when there are genuine buffering problems) scope.PlayerBufferingMinRepeatDelay = 8000;// Minimum delay (in milliseconds) between each pause/play (this is to avoid over pressing pause/play when there are genuine buffering problems)
scope.PlayerBufferingPrerollCheckEnabled = false;// Enable this if you're getting an immediate pause/play/reload as you open a stream (which is causing the stream to take longer to load). One problem with this being true is that it can cause the player to get stuck in some instances requiring the user to press pause/play scope.PlayerBufferingPrerollCheckEnabled = false;// Enable this if you're getting an immediate pause/play/reload as you open a stream (which is causing the stream to take longer to load). One problem with this being true is that it can cause the player to get stuck in some instances requiring the user to press pause/play
scope.PlayerBufferingPrerollCheckOffset = 5;// How far the stream need to move before doing the buffering mitigation (depends on PlayerBufferingPrerollCheckEnabled being true) scope.PlayerBufferingPrerollCheckOffset = 5;// How far the stream need to move before doing the buffering mitigation (depends on PlayerBufferingPrerollCheckEnabled being true)
scope.V2API = false; scope.V2API = false;
@@ -681,6 +681,8 @@ twitch-videoad.js text/javascript
} }
let playerForMonitoringBuffering = null; let playerForMonitoringBuffering = null;
const playerBufferState = { const playerBufferState = {
channelName: null,
hasStreamStarted: false,
position: 0, position: 0,
bufferedPosition: 0, bufferedPosition: 0,
bufferDuration: 0, bufferDuration: 0,
@@ -696,13 +698,30 @@ twitch-videoad.js text/javascript
if (!player.core) { if (!player.core) {
playerForMonitoringBuffering = null; playerForMonitoringBuffering = null;
} else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) { } else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
const m3u8Url = player.core?.state?.path;
if (m3u8Url) {
const fileName = new URL(m3u8Url).pathname.split('/').pop();
if (fileName?.endsWith('.m3u8')) {
const channelName = fileName.slice(0, -5);
if (playerBufferState.channelName != channelName) {
playerBufferState.channelName = channelName;
playerBufferState.hasStreamStarted = false;
playerBufferState.numSame = 0;
//console.log('Channel changed to ' + channelName);
}
}
}
if (player.getState() === 'Playing') {
playerBufferState.hasStreamStarted = true;
}
const position = player.core?.state?.position; const position = player.core?.state?.position;
const bufferedPosition = player.core?.state?.bufferedPosition; const bufferedPosition = player.core?.state?.bufferedPosition;
const bufferDuration = player.getBufferDuration(); const bufferDuration = player.getBufferDuration();
if (position !== undefined && bufferedPosition !== undefined) { if (position !== undefined && bufferedPosition !== undefined) {
//console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition); //console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition + ' state: ' + player.core?.state?.state + ' started: ' + playerBufferState.hasStreamStarted);
// NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play // NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play
if ((!PlayerBufferingPrerollCheckEnabled || position > PlayerBufferingPrerollCheckOffset) && if (playerBufferState.hasStreamStarted &&
(!PlayerBufferingPrerollCheckEnabled || position > PlayerBufferingPrerollCheckOffset) &&
(playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) && (playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
playerBufferState.bufferedPosition == bufferedPosition && playerBufferState.bufferedPosition == bufferedPosition &&
playerBufferState.bufferDuration >= bufferDuration && playerBufferState.bufferDuration >= bufferDuration &&
@@ -990,12 +1009,17 @@ twitch-videoad.js text/javascript
}; };
let wasVideoPlaying = true; let wasVideoPlaying = true;
const visibilityChange = e => { const visibilityChange = e => {
if (typeof chrome !== 'undefined') { const isChrome = typeof chrome !== 'undefined';
const videos = document.getElementsByTagName('video'); const videos = document.getElementsByTagName('video');
if (videos.length > 0) { if (videos.length > 0) {
if (hidden.apply(document) === true || (webkitHidden && webkitHidden.apply(document) === true)) { if (hidden.apply(document) === true || (webkitHidden && webkitHidden.apply(document) === true)) {
wasVideoPlaying = !videos[0].paused && !videos[0].ended; wasVideoPlaying = !videos[0].paused && !videos[0].ended;
} else if (wasVideoPlaying && !videos[0].ended && videos[0].paused && videos[0].muted) { } else {
if (!playerBufferState.hasStreamStarted) {
//console.log('Tab focused. Stream should be active');
playerBufferState.hasStreamStarted = true;
}
if (isChrome && wasVideoPlaying && !videos[0].ended && videos[0].paused && videos[0].muted) {
videos[0].play(); videos[0].play();
} }
} }
+32 -8
View File
@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name TwitchAdSolutions (vaft) // @name TwitchAdSolutions (vaft)
// @namespace https://github.com/pixeltris/TwitchAdSolutions // @namespace https://github.com/pixeltris/TwitchAdSolutions
// @version 36.0.0 // @version 37.0.0
// @description Multiple solutions for blocking Twitch ads (vaft) // @description Multiple solutions for blocking Twitch ads (vaft)
// @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js // @updateURL https://github.com/pixeltris/TwitchAdSolutions/raw/master/vaft/vaft.user.js
// @downloadURL 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== // ==/UserScript==
(function() { (function() {
'use strict'; 'use strict';
const ourTwitchAdSolutionsVersion = 23;// Used to prevent conflicts with outdated versions of the scripts const ourTwitchAdSolutionsVersion = 24;// Used to prevent conflicts with outdated versions of the scripts
if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) { if (typeof window.twitchAdSolutionsVersion !== 'undefined' && window.twitchAdSolutionsVersion >= ourTwitchAdSolutionsVersion) {
console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion); console.log("skipping vaft as there's another script active. ourVersion:" + ourTwitchAdSolutionsVersion + " activeVersion:" + window.twitchAdSolutionsVersion);
window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion; window.twitchAdSolutionsVersion = ourTwitchAdSolutionsVersion;
@@ -34,7 +34,7 @@
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.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.AlwaysReloadPlayerOnAd = false;// Always pause/play when entering/leaving ads
scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play scope.ReloadPlayerAfterAd = true;// After the ad finishes do a player reload instead of pause/play
scope.PlayerReloadMinimalRequestsTime = 1600; scope.PlayerReloadMinimalRequestsTime = 1500;
scope.PlayerReloadMinimalRequestsPlayerIndex = 2;//autoplay scope.PlayerReloadMinimalRequestsPlayerIndex = 2;//autoplay
scope.HasTriggeredPlayerReload = false; scope.HasTriggeredPlayerReload = false;
scope.StreamInfos = []; scope.StreamInfos = [];
@@ -50,7 +50,7 @@
scope.PlayerBufferingSameStateCount = 3;// How many times of seeing the same player state until we trigger pause/play (it will only trigger it one time until the player state changes again) scope.PlayerBufferingSameStateCount = 3;// How many times of seeing the same player state until we trigger pause/play (it will only trigger it one time until the player state changes again)
scope.PlayerBufferingDangerZone = 1;// The buffering time left (in seconds) when we should ignore the players playback position in the player state check scope.PlayerBufferingDangerZone = 1;// The buffering time left (in seconds) when we should ignore the players playback position in the player state check
scope.PlayerBufferingDoPlayerReload = false;// If true this will do a player reload instead of pause/play (player reloading is better at fixing the playback issues but it takes slightly longer) scope.PlayerBufferingDoPlayerReload = false;// If true this will do a player reload instead of pause/play (player reloading is better at fixing the playback issues but it takes slightly longer)
scope.PlayerBufferingMinRepeatDelay = 12000;// Minimum delay (in milliseconds) between each pause/play (this is to avoid over pressing pause/play when there are genuine buffering problems) scope.PlayerBufferingMinRepeatDelay = 8000;// Minimum delay (in milliseconds) between each pause/play (this is to avoid over pressing pause/play when there are genuine buffering problems)
scope.PlayerBufferingPrerollCheckEnabled = false;// Enable this if you're getting an immediate pause/play/reload as you open a stream (which is causing the stream to take longer to load). One problem with this being true is that it can cause the player to get stuck in some instances requiring the user to press pause/play scope.PlayerBufferingPrerollCheckEnabled = false;// Enable this if you're getting an immediate pause/play/reload as you open a stream (which is causing the stream to take longer to load). One problem with this being true is that it can cause the player to get stuck in some instances requiring the user to press pause/play
scope.PlayerBufferingPrerollCheckOffset = 5;// How far the stream need to move before doing the buffering mitigation (depends on PlayerBufferingPrerollCheckEnabled being true) scope.PlayerBufferingPrerollCheckOffset = 5;// How far the stream need to move before doing the buffering mitigation (depends on PlayerBufferingPrerollCheckEnabled being true)
scope.V2API = false; scope.V2API = false;
@@ -692,6 +692,8 @@
} }
let playerForMonitoringBuffering = null; let playerForMonitoringBuffering = null;
const playerBufferState = { const playerBufferState = {
channelName: null,
hasStreamStarted: false,
position: 0, position: 0,
bufferedPosition: 0, bufferedPosition: 0,
bufferDuration: 0, bufferDuration: 0,
@@ -707,13 +709,30 @@
if (!player.core) { if (!player.core) {
playerForMonitoringBuffering = null; playerForMonitoringBuffering = null;
} else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) { } else if (state.props?.content?.type === 'live' && !player.isPaused() && !player.getHTMLVideoElement()?.ended && playerBufferState.lastFixTime <= Date.now() - PlayerBufferingMinRepeatDelay && !isActivelyStrippingAds) {
const m3u8Url = player.core?.state?.path;
if (m3u8Url) {
const fileName = new URL(m3u8Url).pathname.split('/').pop();
if (fileName?.endsWith('.m3u8')) {
const channelName = fileName.slice(0, -5);
if (playerBufferState.channelName != channelName) {
playerBufferState.channelName = channelName;
playerBufferState.hasStreamStarted = false;
playerBufferState.numSame = 0;
//console.log('Channel changed to ' + channelName);
}
}
}
if (player.getState() === 'Playing') {
playerBufferState.hasStreamStarted = true;
}
const position = player.core?.state?.position; const position = player.core?.state?.position;
const bufferedPosition = player.core?.state?.bufferedPosition; const bufferedPosition = player.core?.state?.bufferedPosition;
const bufferDuration = player.getBufferDuration(); const bufferDuration = player.getBufferDuration();
if (position !== undefined && bufferedPosition !== undefined) { if (position !== undefined && bufferedPosition !== undefined) {
//console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition); //console.log('position:' + position + ' bufferDuration:' + bufferDuration + ' bufferPosition:' + bufferedPosition + ' state: ' + player.core?.state?.state + ' started: ' + playerBufferState.hasStreamStarted);
// NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play // NOTE: This could be improved. It currently lets the player fully eat the full buffer before it triggers pause/play
if ((!PlayerBufferingPrerollCheckEnabled || position > PlayerBufferingPrerollCheckOffset) && if (playerBufferState.hasStreamStarted &&
(!PlayerBufferingPrerollCheckEnabled || position > PlayerBufferingPrerollCheckOffset) &&
(playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) && (playerBufferState.position == position || bufferDuration < PlayerBufferingDangerZone) &&
playerBufferState.bufferedPosition == bufferedPosition && playerBufferState.bufferedPosition == bufferedPosition &&
playerBufferState.bufferDuration >= bufferDuration && playerBufferState.bufferDuration >= bufferDuration &&
@@ -1001,12 +1020,17 @@
}; };
let wasVideoPlaying = true; let wasVideoPlaying = true;
const visibilityChange = e => { const visibilityChange = e => {
if (typeof chrome !== 'undefined') { const isChrome = typeof chrome !== 'undefined';
const videos = document.getElementsByTagName('video'); const videos = document.getElementsByTagName('video');
if (videos.length > 0) { if (videos.length > 0) {
if (hidden.apply(document) === true || (webkitHidden && webkitHidden.apply(document) === true)) { if (hidden.apply(document) === true || (webkitHidden && webkitHidden.apply(document) === true)) {
wasVideoPlaying = !videos[0].paused && !videos[0].ended; wasVideoPlaying = !videos[0].paused && !videos[0].ended;
} else if (wasVideoPlaying && !videos[0].ended && videos[0].paused && videos[0].muted) { } else {
if (!playerBufferState.hasStreamStarted) {
//console.log('Tab focused. Stream should be active');
playerBufferState.hasStreamStarted = true;
}
if (isChrome && wasVideoPlaying && !videos[0].ended && videos[0].paused && videos[0].muted) {
videos[0].play(); videos[0].play();
} }
} }