Skip to content
Closed
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
2 changes: 1 addition & 1 deletion dist/resources.json

Large diffs are not rendered by default.

67 changes: 34 additions & 33 deletions resources/brave-yt-sabr-fix.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
//
// Two-pronged fix:
//
// 1. SPA navigation: When clicking between videos without a page reload,
// the player reuses the old SABR session (which may already have a
// backoff). On yt-navigate-finish, we set isInlinePlaybackNoAd on the
// player's video data (telling it there's no ad slot), then call
// cancelPlayback() + loadVideoById() to force a new SABR session.
// 1. Fresh ad-free session: When Prong 2 detects a real backoff, set
// isInlinePlaybackNoAd on the player's video data (telling it there's no
// ad slot), then call cancelPlayback() + loadVideoById() to force a new
// SABR session. Guarded per video and only before playback starts, so
// no-ad videos and mid-playback pacing backoffs are left alone.
//
// 2. SABR response patching (full page loads): Intercept SABR streaming
// responses and rewrite backoffTimeMs in the protobuf. This handles
Expand All @@ -36,34 +36,30 @@
// Premium accounts have no ads and no backoff, nothing to do.
if (document.querySelector('a#logo[title*="Premium" i]')) return;

// Prong 1: On SPA navigation, force a new ad-free SABR session.
// Set isInlinePlaybackNoAd on the video data so the new session
// doesn't get an ad slot, then tear down the old session and start fresh.
// Initialize to the current vid so the initial yt-navigate-finish
// (which fires on page load completion, not just SPA nav) is treated
// as a duplicate and skipped. Avoids tearing down playback on refresh.
// We skip teardown when there's no prior session to clear.
let lastReloadedVid = new URL(window.location.href).searchParams.get('v') || '';
window.addEventListener('yt-navigate-finish', () => {
// Prong 1: Force a fresh ad-free SABR session, driven by Prong 2 when it
// sees a real backoff. Set isInlinePlaybackNoAd so the new session gets no
// ad slot, then tear down the current session and reload. Guarded per video
// (one reload each, so a session that still backs off falls back to patching
// rather than looping) and skipped once playback has started — a no-ad video
// has no backoff, and a mid-playback backoff is normal pacing.
let reloadedVid = null;
function forceFreshSession() {
const vid = new URL(window.location.href).searchParams.get('v');
if (!vid || vid === lastReloadedVid) return;
const hadPriorSession = lastReloadedVid !== '';
lastReloadedVid = vid;
if (!hadPriorSession) return;

setTimeout(() => {
const player = document.querySelector('#movie_player');
if (!player?.cancelPlayback || !player?.loadVideoById) return;

// Set the no-ad flag before reloading so the new session picks it up
const vd = player.getVideoData?.();
if (vd) vd.isInlinePlaybackNoAd = true;

player.cancelPlayback();
player.loadVideoById(vid);
log('forced new SABR session for', vid);
}, 100);
});
if (!vid || vid === reloadedVid) return;
const video = document.querySelector('video');
if (video && video.currentTime > 1) return;
reloadedVid = vid;
const player = document.querySelector('#movie_player');
if (!player?.cancelPlayback || !player?.loadVideoById) return;

// Set the no-ad flag before reloading so the new session picks it up
const vd = player.getVideoData?.();
if (vd) vd.isInlinePlaybackNoAd = true;

player.cancelPlayback();
player.loadVideoById(vid);
log('forced fresh ad-free session for', vid);
}

// Prong 2: Intercept SABR responses and patch backoffTimeMs.
// Tee the body so media chunks (>=1000 bytes) pass through untouched
Expand Down Expand Up @@ -96,7 +92,9 @@
return new Response(pass, reinit);
}
log('small response rn=' + rn, 'size=' + bytes.length);
patchBackoffField(bytes, rn);
// A real backoff blocks initial playback; force a fresh
// ad-free session (guarded per video, skipped mid-playback).
if (patchBackoffField(bytes, rn)) forceFreshSession();
const out = new Response(bytes, reinit);
try {
Object.defineProperty(out, 'url', { value: response.url, configurable: true });
Expand Down Expand Up @@ -142,6 +140,7 @@
// flag. We rewrite the varint in place, keeping the same byte count
// so the message structure stays valid.
function patchBackoffField(bytes, rn) {
let patched = false;
for (let i = 0; i < bytes.length - 2; i++) {
if (bytes[i] !== 0x20) continue;
let val = 0, shift = 0, end = i + 1;
Expand All @@ -160,8 +159,10 @@
remaining >>>= 7;
}
bytes[pos] = remaining & 0x7f;
patched = true;
}
}
return patched;
}

if (DEBUG) {
Expand Down
Loading