Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion mods/features/adblock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');
}

Expand Down
48 changes: 48 additions & 0 deletions mods/features/playlistBatchCollect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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
Expand All @@ -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,
});
Expand Down