Repository navigation
Releases
Releases · Helli24/TwitchAdSolutions
vaft v68.11.0
Fixed
- Ads played after the player had been paused for 30 minutes — field log (v107): a stream opened at 18:13, the player paused in a background tab, and at 21:02 an ad played on resume with
Untracked media playlist … | ad markers: stitched-ad, twitch-stitched (ADS NOT BLOCKED) | tracked variant URLs: 0andAd watchdog — Twitch ad UI visible, vaft saw NO ad break. The ad was marked normally; vaft no longer knew the playlist.pruneStreamInfos()runs every 5 minutes and dropped every stream that had not been polled for 30 minutes. A paused player polls nothing, but it keeps its variant URLs and resumes with them, so after the pruneprocessM3U8passed the playlist through unfiltered for the rest of the page session. A stale stream is now pruned only while another stream in the same worker is being polled (the user moved on to another channel); a paused player keeps its stream registered. vaft only
Added
- Player reload when an unknown playlist carries ads (
UntrackedReload, fork default on) — safety net for any other way the stream registration can get lost. When a live playlist that vaft does not track carries ad markers, the worker asks for one hard player reload (ReloadPlayerkinduntracked-ads, handled likeworker-recovery: it replaces a healthy player and refreshes the token). The new media player instance requests the master playlist again throughhookWorkerFetch, which registers the stream, and the ad is blocked from then on. Limits: one reload per 30 s and two per 10 minutes, then a singlegiving upline. LogsUntracked playlist carries ads — reloading the player …. Opt-out:twitchAdSolutions_untrackedReload='false'. vaft only
Tests
- 371 checks:
pruneStreamInfos(paused stream kept, left streams pruned),planUntrackedAdReload(limits, giving up, opt-out), the untracked branch ofprocessM3U8end to end,isRecoveryReloadKind. - New check on the worker blob template: every function serialized into it (
${name.toString()}) is declared and every injected option exists indeclareOptions. A missing one is aReferenceErrorwhen Twitch constructs its worker, which no syntax check sees.
vaft v68.10.3
Result of a review of all fork changes (68.6.0-68.10.2).
Fixed
- Backup playlists numbered below 0 at prerolls — at a preroll the native playlist is numbered from 0 with 3 segments while the backup carries 14, so
AlignBackupSequenceshifted the backup onto negative numbers (field logs:backup 957..970 → -11..2,backup 13..26 → -11..2).#EXT-X-MEDIA-SEQUENCEis a non-negative integer in HLS; what a player does with-11is undefined.shiftMediaSequence()now drops the oldest segments that would land below 0 (with theirPROGRAM-DATE-TIME), which leaves exactly the segments that line up with native (0..2), keeps at least one segment and counts a dropped#EXT-X-DISCONTINUITYinto#EXT-X-DISCONTINUITY-SEQUENCE. The log line says how many segments were dropped. vaft only - A Twitch re-mute could be taken for the user's mute —
trackUserMuteIntent()treated a mute within 1.5 s of any click or keypress as the user's, including typing in chat and vaft's own pre-mute at the start of a hard reload. Either marks the player as "muted by the user" and switches off the silent re-mute recovery, so the stream could stay muted after an ad break. Only input aimed at the player counts now (click, drag or wheel on the player, keys pressed outside text fields), vaft's own pre-mute is ignored, and dragging the volume slider is tracked for the whole drag. vaft only
Changed
- Ad watchdog output — requests are grouped by host (
host ×count: /path , /path (+N more paths), most recent host first) instead of listing 25 single URLs, so a busy chat (emote images) or a run of video segments cannot push the interesting hosts out; the main-thread buffer holds 300 entries instead of 100. The worker request list is now asked from the player's own worker (player.core.worker) when it is hooked; before, the request went to the most recently constructed worker, which is not necessarily the player's. vaft only noteUntrackedPlaylist()ignores VOD and ended playlists, which are never tracked and would only use up the 5-URL log budget. vaft only- Comments that the fork's helper functions had separated from their functions are back in place. No code change.
Tests and CI
- The two vaft files are compared in CI —
tests/scripts.jstokenizesvaft.user.jsandvaft-ublock-origin.jsand failsnpm teston any code difference beyond the known upstream ones (host check line, comments, two log strings). Until now the pair was only compared by hand. 303 checks. - New
npm run syntax(tests/syntax.js) parses all ten shipped scripts, including the twotwitch-brave-fixfiles that are release assets but were never syntax-checked; both workflows use it instead of two copied shell loops. - Release workflow: the version commit and the tag are pushed atomically (a failed tag push for v68.10.0 had left a version commit without a tag); the release is published with GitHub's own
ghCLI instead of a third-party action holding the write token; the version string is validated and passed to the shell through the environment.validate.ymlruns with a read-only token.
vaft v68.10.2
Fixed
- Ad watchdog used up its log on a false positive — field log: right at page load, with the player still paused and without a source, the watchdog reported
element [data-a-target="ax-overlay"] "". That empty element stays on screen, so the once-per-appearance log was spent and never re-armed, and a real ad later in the session would not have been reported.ax-overlayis removed from the selectors, and every selector match now needs visible text (Twitch's ad label and countdown always have some). The main-thread request log also includes loads from before the first check now (PerformanceObserverwithbuffered: true). vaft only
vaft v68.10.1
Debug Logging
- Untracked media playlists — field log: after the preroll at 21:18 vaft logged nothing for four hours while two midrolls with Twitch's ad UI played in the stream's own
<video>(fed viasrcObject, i.e. through the player worker). The player worker was hooked (the unhooked-worker check stayed silent) and neitherPossible missed adnor a CSAI request was logged. One path inprocessM3U8is completely silent: a media playlist whose URL is not inStreamInfosByUrlis passed through unfiltered, ad segments included. That happens when the player got its variant URLs from a master playlist vaft never parsed. New worker-serializednoteUntrackedPlaylist()logsUntracked media playlist — <host/path> … | ad markers: …on the first sighting of each such URL (max 5 per worker) andUntracked media playlist carries ads (NOT BLOCKED)at most every 30 s while it carries ad markers. Query strings are never logged. Logging only. vaft only
vaft v68.10.0
Debug Logging
- Ad watchdog — field reports: midrolls with Twitch's ad UI (
<channel> is taking an ad break; stick around to support the stream! Ad (0:02), stream audio gone) played while vaft logged nothing at all, so the path the ad took could not be reconstructed afterwards. The buffer monitor now checks whether Twitch's ad UI is visible (ad label / countdown attributes, with a text fallback inside the player fortaking an ad break/Ad (m:ss)). Once per appearance it logs three or fourAd watchdog —lines: whether vaft saw an ad break, the matched element and its text, whether the player worker is hooked; every<video>(source host, orsrcObjecttype for MSE-in-worker playback) and visible<iframe>; the hosts and paths requested in the last 30 s on the main thread (PerformanceObserver) and in the hooked worker (new worker-serializeddescribeRequestUrl(), ring bufferRecentWorkerRequests, fetched via aGetRecentRequestsmessage). Query strings and fragments are never logged. Read-only: nothing is hidden, reloaded or blocked. vaft only
vaft v68.9.2
Tests
- Unit tests run against the shipped scripts —
tests/test.jsused hand-copied snapshots of the functions it tested, so it never exercised the real code and had drifted (it still asserted thattwitch-triggeris not an ad signifier, which upstream changed). Newtests/scripts.jsparsesvaft.user.jsandvaft-ublock-origin.jswith acorn and evaluates the named declarations in avmcontext (worker functions afterdeclareOptions(globalThis), like the worker blob). Every suite runs against both formats; a deliberate one-line change in only one file fails only that file's run. 174 checks, including new ones for the fork features: backup sequence alignment with the offsets from the field logs (+11, +14, 0, 0, −968), unhooked-worker recovery (one player reload, then one page reload, the 10-minute guard and the opt-out) and user mute intent. package.jsonwith acorn as the only dev dependency; CI and the release workflow usenpm ci,npx acornandnpm test.
Changed
AlignBackupSequence: the offset computation and the sequence shift moved from inline code inprocessM3U8into the worker-serialized helpersgetBackupSequenceOffset()andshiftMediaSequence()so they can be tested. Same behaviour and log line. vaft only
vaft v68.9.1
Fixed
- A deliberate mute was undone after ad breaks — field log:
Hard reload backstop unmute fired — element was still muted at 5500ms (initial: already-muted on entry — recovering from silent Twitch re-mute)right after a post-ad hard reload, while the user had muted the stream themselves.RecoverFromSilentMutetreats any mute on entry to a hard reload as Twitch's silent re-mute once vaft has unmuted at least once in the session, which is true after the first ad break. NewtrackUserMuteIntent(): a mute or unmute of the player<video>within 1.5 s of a click or keypress (Twitch's mute button, themkey) is recorded as the user's choice (playerBufferState.userMuteIntent), and the recovery is skipped while the user wants it muted. Mutes without user input (Twitch's own) are still recovered. LogsUser muted the player/keeping it muted. vaft only