Skip to content

Latest commit

 

History

History
103 lines (73 loc) · 9.78 KB

File metadata and controls

103 lines (73 loc) · 9.78 KB

TwitchAdSolutions

Fork of pixeltris/TwitchAdSolutions (archived). Remote origin = pixeltris, remote master = ryanbr.

Worker Blob Serialization (CRITICAL)

Functions serialized via .toString() into the Web Worker blob CANNOT reference outer-scope variables. This includes: processM3U8, stripAdSegments, hookWorkerFetch, getAccessToken, gqlRequest, hasAdTags, getMatchedAdSignifiers, notifyAdComplete, getStreamUrlForResolution, parseAttributes, getServerTimeFromM3u8, replaceServerTimeInM3u8, pruneStreamInfos, createStreamInfo, getWasmWorkerJs.

  • Declare variables in declareOptions() (also serialized) or in the inline blob template literal
  • To pass window-scope values to the worker, inject after declareOptions(self): e.g. ReloadPlayerAfterAd = ${ReloadPlayerAfterAd};
  • Regex hoisting or referencing outer-scope variables from these functions causes ReferenceError

Files

Synced pairs (same logic, different format):

  • vaft/vaft.user.js + vaft/vaft-ublock-origin.js
  • vaft/vaft_testing.user.js + vaft/vaft-testing-ublock-origin.js
  • video-swap-new/video-swap-new.user.js + video-swap-new/video-swap-new-ublock-origin.js
  • video-swap-new/video-swap-new-ublock-origin-testing.js (testing only)
  • strip/strip.user.js (standalone)

uBlock files have twitch-videoad.js text/javascript as line 1 (not valid JS — uBlock resource header).

Naming Differences

vaft video-swap-new strip
Signifiers AdSignifiers AD_SIGNIFIERS N/A
URL patterns AdSegmentURLPatterns AD_SEGMENT_URL_PATTERNS N/A
Player type ForceAccessTokenPlayerType OPT_FORCE_ACCESS_TOKEN_PLAYER_TYPE ForceAccessTokenPlayerType (default site)
Reload after ad ReloadPlayerAfterAd ReloadPlayerAfterAd ReloadPlayerAfterAds

Versions

Bump @version (userscript header) and ourTwitchAdSolutionsVersion together for functional changes. Current: vaft 68.4.0/85, video-swap-new 1.86/54, strip 1.9/26. Testing: vaft 666.0.0/666, video-swap-new -/619.

localStorage Config

All read at init, injected into worker blob:

  • twitchAdSolutions_reloadPlayerAfterAdtrue/false, default true
  • twitchAdSolutions_playerType — string, default popout
  • twitchAdSolutions_pinBackupPlayerTypetrue/false, default false (vaft default true)
  • twitchAdSolutions_hideAdOverlaytrue to hide the internal .tas-adblock-overlay banner (SDA wrapper hide always runs), default not set
  • twitchAdSolutions_reloadCooldownSeconds — number, default 30 (0 to disable)
  • twitchAdSolutions_disableReloadCaptrue to revert to unlimited reloads
  • twitchAdSolutions_driftCorrectionRate — number, default 1.1 (0 to disable)
  • twitchAdSolutions_earlyReloadPollThreshold — number, default 3 (0 to disable; thin cache overrides to 1)
  • twitchAdSolutions_preferLowQualityBackup — hybrid mode (sticky escape hatch + autoplay last-resort backup), default true; set to false to disable (vaft only)
  • twitchAdSolutions_backupSwapFirst — on ad detect, immediately swap to backup player-type m3u8 (TTV-AB-style) instead of sticky CSAI strip. Default true as of v63.0.0; set to false for legacy strip-first path (vaft only)
  • twitchAdSolutions_fastAutoplayFirstTry — opt-out (default true as of v67.1.0). Prepend autoplay (360p) to position 0 of the iteration when the prior break committed autoplay via PreferLowQualityBackup escape hatch. Saves ~1.5-2s of probe-loop buffering. Auto-resets when a Source-tier type wins (channel recovered) so quality returns to full automatically. Set to 'false' to force full Source-tier probe on every break (vaft only)
  • twitchAdSolutions_recoverFromSilentMute — opt-out (default true). On hard reload, if the element is already muted but vaft has successfully unmuted at any point earlier this session, recover via the 5500ms backstop (Twitch's silent re-mute pattern — issue #200). Disable via 'false' if you deliberately mute mid-session and want that preserved across reloads. Users muted from session start are always respected (vaftEverUnmuted=false short-circuits the recovery check). vaft only.
  • twitchAdSolutions_disableAdSpoofing — opt-in via 'false' (default 'true' — spoofing is OFF). When enabled, on ad detect vaft fires the GQL ad-tracking beacons (video_ad_impression, video_ad_quartile_complete × 4, video_ad_pod_complete) that Twitch's player would have sent if the ad had played normally. Originally intended to mimic the ad-completion signal Twitch expects and may reduce detection escalation — but the always-100%-watched + audible + visible beacon pattern may itself fingerprint as anomalous (silently, without GQL rejection) and trigger detection escalation in the other direction (observed correlation with CSAI ads reaching committed backups + TTV-AB maintainer hypothesis). Default flipped from on→off after v68.2.0. Set to 'false' to re-enable (for A/B testing whether spoofing affects ad break duration / CSAI escalation on your channels). Failures swallowed (never blocks ad-block flow). vaft only.

CSAI vs SSAI

  • SSAI (Server-Side Ad Insertion) — ads embedded in m3u8 segments. Blockable by stripping.
  • CSAI (Client-Side Ad Insertion) — ads delivered via edge.ads.twitch.tv, outside m3u8. Not blockable at m3u8 level. Detected via fetch/XHR hooks for logging only.
  • When hadStrippedSegments === false (CSAI-only), skip reload entirely to prevent cascade.

Key Architecture (vaft)

  • Buffer monitor (monitorPlayerBuffering) — polls player state every 1-3s (visibility-aware). Detects stalls, fires pause/play or reload.
  • Reload cooldown — 30s default, auto-escalates to 90s if 3+ reloads in 5 minutes.
  • Reload cap — buffer monitor reloads at most once per recovery window (recoveryReloadUsed).
  • Grace periods — 15s after reload, 10s after backup switch. Buffer monitor skips fixes during these.
  • Drift correctionstartDriftCorrection(videoElement) shared function. 1.1× playback rate, 30s safety timeout. Restarts fresh on re-entry (clears stale timers). Used by post-reload drift and buffer gap seek.
  • Reload routing — worker → main ReloadPlayer messages carry a kind field. doTwitchPlayerTask(isPausePlay, isReload, reloadKind) picks setSrc params: kind === 'early' → hard reload (isNewMediaPlayerInstance: true, refreshAccessToken: true, new session); otherwise soft reload. Early reload sites (both sticky + normal paths) AND post-ad reload sites send kind: 'early' to force hard reload. HEVC force reload stays soft (codec change, no strip involved). Hard reload flushes the MediaSource buffer — required after strip activity (BLANK_MP4 injection, recovery replay) to avoid audio/video desync from accumulated timestamp drift.
  • Early reload — fires during prolonged all-stripped freeze. Threshold: 3 polls (~6s), or 1 poll when recovery cache <3 segments (thin-cache fast path). Budget: max(1, PodLength) or max(2, PodLength) when thin. EarlyReloadTriggered resets on "still ads" (both sticky + normal paths) to allow budget-based re-fire. Override via twitchAdSolutions_earlyReloadPollThreshold.
  • Sticky CSAI fast path — once a break enters CSAI fast path (all segments live), stays on it for the whole break. Has its own early-reload trigger + EarlyReloadAwaitingResult check (normal-path check unreachable due to early return).
  • Latency-aware reload health check — measures seekable.end - currentTime before skipping post-ad reload. If >7s behind live or seekable unavailable/garbage, proceeds with reload. Guards against 2^30 sentinel values via Number.isFinite + 3600s cap.
  • User pause intent — tracks video pause/play events to distinguish user vs script pauses. weJustPaused only resets when player wasn't paused (guards against clearing intent during stall recovery).
  • Stale player refplayerForMonitoringBuffering = null on reload to force re-acquisition.
  • StreamInfo factorycreateStreamInfo() declares all fields up-front (46 fields vaft, 31 fields video-swap-new). Serialized into worker blob.

Debug Logging

All logs use [AD DEBUG] prefix. Logs in .toString() functions must use console.log directly. Some logs are deduped (once per ad break or once per page load) to prevent console spam.

Validation

npx acorn --ecma2022 file.js (skip line 1 for uBlock files: tail -n +2 file.js | npx acorn --ecma2022). GitHub Actions validates on push/PR.

Testing

Testing files include experimental features (ad completion spoofing, lower thresholds, removed pruneStreamInfos). Don't apply to main scripts without explicit request. Testing-script-only changes commit directly to master; PRs are for release scripts.

Backup Player Types

  • vaft: embed, site, popout, mobile_web (autoplay removed — gets stuck in loading circle on transition back)
  • video-swap-new: embed, popout, mobile_web (autoplay + picture-by-picture removed)

Ad Overlay Hiding

hideTwitchAdOverlays() hides one overlay type during ad blocking:

  • Stream display ads (SDA) — via exact [data-test-selector="sda-wrapper"] selector, no parent walking

Called on every buffer monitor tick. Guards via dataset.tasHidden to skip already-hidden elements.

Previously removed:

  • Turbo promo / "allow ads" overlay hide (PR #143) — used .player-overlay-background which is Twitch's generic modal scrim (also used for content gates, error dialogs, subscription warnings). Too broad to use safely.
  • Ad-break-card text match (PR #141) — scanned span/p/h1/h2/h3 text for "taking an ad break" phrases then walked up via fuzzy [class*="overlay"] + parentElement fallback. Could hide player controls on false matches. TTV-AB doesn't attempt this either.

Only keep overlay hides that use exact attribute selectors with no parent walking.