From 7d165e1e81a0b03071f3a7301492678daeeb642d Mon Sep 17 00:00:00 2001 From: KrX3D Date: Sat, 29 Aug 2026 21:55:12 +0200 Subject: [PATCH] perf: build the full-playlist cache from YouTube's own batches, not a re-download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cache worked on-device, but the wait before it did was almost entirely self-inflicted: 19:50:42.395 .. 19:50:43.128 YouTube delivers every batch 0.73s 19:50:45.006 .. 19:50:52.963 our collector re-fetches them 8.0s 19:50:52.966 full_cache.stored -> reloading 19:50:53.135 full_cache.injected YouTube had the entire playlist in hand at 43.128. The collector then spent eight seconds re-downloading exactly that, paced at 2.5s per request, and only then could the reload happen — so the helper tiles sat on screen for about ten seconds instead of one. adblock.js already intercepts every continuation response before it filters them, so the raw items can be taken straight from there. noteContinuationBatch accumulates them, and when a response arrives carrying no continuation token the playlist is complete: cache it and reload immediately, with no extra network work at all. The hook is placed BEFORE filterContinuationItems deliberately — after it, an all-watched batch is reduced to the single kept helper. Also cancels the background collector once the native path has completed, since it would otherwise keep issuing requests for data already held. The flag is cleared on navigation and at the start of each collection, so a finished playlist cannot block collection on the next one. The collector is still worth keeping for playlists that are NOT fully watched: there YouTube only fetches as the user scrolls, so the accumulator would fill slowly and the prefetch still does useful work. --- mods/features/adblock.js | 7 +++- mods/features/playlistBatchCollect.js | 48 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/mods/features/adblock.js b/mods/features/adblock.js index 1f6d2e99..18e8fa82 100644 --- a/mods/features/adblock.js +++ b/mods/features/adblock.js @@ -5,7 +5,7 @@ import { timelyAction, longPressData, MenuServiceItemRenderer, ShelfRenderer, Ti import { PatchSettings } from '../ui/customYTSettings.js'; import { t } from 'i18next'; import './logServer.js'; -import { autoStartCollect, getCachedFullPlaylist, noteInitialPlaylistContents } from './playlistBatchCollect.js'; +import { autoStartCollect, getCachedFullPlaylist, noteInitialPlaylistContents, noteContinuationBatch } from './playlistBatchCollect.js'; import { appendFileOnlyLog, detectAndStorePage, @@ -992,6 +992,11 @@ JSON.parse = function () { const hasContinuation = !!plc?.continuations; storePlaylistContinuationToken(plc.continuations, 'plc'); appendFileOnlyLog('playlist.continuation.detected', { detectedPage, itemCount: Array.isArray(plc.contents) ? plc.contents.length : 0, hasContinuation }); + // Feed the raw batch to the full-playlist accumulator BEFORE filtering, + // so it collects real items rather than the single kept helper. This is + // the same data the background collector would re-download, only it is + // already here and roughly ten seconds sooner. + noteContinuationBatch(String(window.location?.hash || ''), plc.contents, hasContinuation); plc.contents = filterContinuationItems(plc.contents, detectedPage, hasContinuation, 'playlist.continuation'); } diff --git a/mods/features/playlistBatchCollect.js b/mods/features/playlistBatchCollect.js index 92705812..6e8d175a 100644 --- a/mods/features/playlistBatchCollect.js +++ b/mods/features/playlistBatchCollect.js @@ -134,6 +134,10 @@ let _lastBrowseHeaders = null; function _clearState() { window.__ttPrefetchedBatch = null; window.__ttPrefetchStarted = false; + // Must be cleared too, or one completed playlist would permanently block + // collection on every later one. + window.__ttCollectCancel = false; + window.__ttContinuationAcc = null; } window.addEventListener('hashchange', _clearState); window.addEventListener('popstate', _clearState); @@ -264,6 +268,7 @@ async function _collectAll(url, plc, context, headers) { try { while (continuations && batchesLoaded < MAX && !abort.signal.aborted) { + if (window.__ttCollectCancel) { _log('playlist.batch_collect.cancelled', { batch: batchesLoaded }); break; } const token = _getToken(continuations); if (!token) { _log('playlist.batch_collect.no_token', { @@ -467,6 +472,47 @@ function maybeReloadForFullPlaylist(key) { } } +// Accumulate the playlist from YOUTUBE'S OWN continuation responses. +// +// The background collector re-downloads batches YouTube has usually already +// fetched, and it is far slower doing it. Measured on a 68-video all-watched +// playlist: YouTube delivered every batch between 19:50:42.395 and +// 19:50:43.128 — 0.73s — while the collector, paced at 2.5s per fetch, ran +// from 19:50:45.006 to 19:50:52.963 and only then triggered the reload. That +// left roughly ten seconds of helper tiles on screen for data already in hand. +// +// adblock.js already sees every continuation response before it filters them, +// so it can hand the raw items straight here. When a response arrives with no +// continuation token the playlist is complete, and the reload can happen +// immediately instead of waiting for a redundant re-download. +export function noteContinuationBatch(key, contents, hasMore) { + if (!configRead('enablePlaylistBatchCollect')) return; + if (!Array.isArray(contents) || !contents.length) return; + if (getCachedFullPlaylist(key)) return; // already complete; this is the reloaded pass + + const acc = window.__ttContinuationAcc; + if (!acc || acc.key !== key) { + window.__ttContinuationAcc = { key, contents: contents.slice() }; + } else { + acc.contents = acc.contents.concat(contents); + } + + if (hasMore) return; + + // No continuation token: this was the last batch, so initial + everything + // accumulated is the whole playlist. + const collected = window.__ttContinuationAcc.contents; + _log('playlist.full_cache.from_native', { key, collected: collected.length }); + if (storeFullPlaylist(key, collected)) { + // YouTube already delivered the whole playlist, so the collector is now + // re-downloading data we hold. Cancel it rather than let it run on for + // several more seconds of pointless requests. + window.__ttCollectCancel = true; + window.__ttContinuationAcc = null; + maybeReloadForFullPlaylist(key); + } +} + // ── Auto-trigger on playlist page load ──────────────────────────────────────── // Called by adblock.js right after the initial playlist page's own // continuation token is parsed (topPlaylistRenderer.continuations) — starts @@ -489,6 +535,8 @@ export function autoStartCollect(continuations) { const token = _getToken(continuations); if (!token) return; + // Fresh run: clear any cancel left over from a previous playlist. + window.__ttCollectCancel = false; _log('playlist.batch_collect.auto_triggered', { headerKeys: _lastBrowseHeaders ? Object.keys(_lastBrowseHeaders) : null, });