// Persistent broadcast browser helper. Connects over CDP and injects one // watcher into every tab (current and future) that: // 1. Auto-skips YouTube ads - clicks "Skip ad" the instant it appears, closes // overlay ads, and fast-forwards unskippable ads (seek-to-end + 16x + mute) // so they clear in ~1s. The pre-ad muted/playbackRate are SAVED and // RESTORED when the ad ends, so the main video is never left muted/fast. // 2. Applies the subtitle rule per video: captions OFF by default, but a // Korean track is turned ON when the video offers one. Runs once per video. // Self-contained: no extension, no network/hosts changes. Reconnects across // Chrome restarts. // // node bot/scripts/stream-test/broadcast-helper.mjs (CDP_PORT, default 9222) import { chromium } from 'playwright'; const CDP = process.env.CDP_PORT || '9222'; const WATCH = `(() => { if (window.__ytBroadcast) return; window.__ytBroadcast = true; let adSaved = null; // {muted, rate} captured when an ad starts const capWant = {}; // videoId -> 'ko' | 'off' (desired, decided once) const capTries = {}; // videoId -> attempts to read the tracklist const adTick = () => { const p = document.getElementById('movie_player'); const adShowing = !!(p && p.classList && p.classList.contains('ad-showing')); const v = document.querySelector('video'); const skip = document.querySelector( '.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button, .ytp-ad-skip-button-container button'); if (skip) skip.click(); document.querySelectorAll('.ytp-ad-overlay-close-button, .ytp-ad-overlay-close-container button').forEach((b) => b.click()); if (adShowing) { if (adSaved === null && v) adSaved = { muted: v.muted, rate: v.playbackRate }; if (v) { v.muted = true; if (isFinite(v.duration) && v.duration > 0) { try { v.currentTime = v.duration; } catch {} } v.playbackRate = 16; } } else if (adSaved !== null && v) { // ad finished: restore exactly what the user had before the ad v.muted = adSaved.muted; v.playbackRate = adSaved.rate; adSaved = null; } return adShowing; }; const capTick = (adShowing) => { if (adShowing) return; // don't touch captions while an ad plays const p = document.getElementById('movie_player'); if (!p || !p.getOption || !p.getVideoData) return; const vid = p.getVideoData().video_id; if (!vid) return; // Decide the desired state once per video (off, or Korean if offered). if (capWant[vid] === undefined) { capTries[vid] = (capTries[vid] || 0) + 1; let tracks = []; try { p.loadModule('captions'); tracks = p.getOption('captions', 'tracklist') || []; } catch {} if (tracks.length) capWant[vid] = tracks.find((t) => /^ko/i.test(t.languageCode || '')) ? 'ko' : 'off'; else if (capTries[vid] > 16) capWant[vid] = 'off'; // no tracks: keep it off else return; // tracklist not ready yet } // Enforce it every tick so YouTube cannot silently re-enable captions. let curLc = ''; try { const c = p.getOption('captions', 'track'); curLc = (c && c.languageCode) || ''; } catch {} if (capWant[vid] === 'ko') { if (!/^ko/i.test(curLc)) { let tracks = []; try { tracks = p.getOption('captions', 'tracklist') || []; } catch {} const ko = tracks.find((t) => /^ko/i.test(t.languageCode || '')); if (ko) { try { p.setOption('captions', 'track', { languageCode: ko.languageCode }); } catch {} } } } else if (curLc) { // want off but a track is on -> turn it off try { p.setOption('captions', 'track', {}); } catch {} try { p.unloadModule('captions'); } catch {} } }; setInterval(() => { let adShowing = false; try { adShowing = adTick(); } catch {} try { capTick(adShowing); } catch {} }, 250); })();`; async function arm(page) { try { await page.addInitScript(WATCH); } catch {} // survives navigations try { await page.evaluate(WATCH); } catch {} // arm the already-loaded doc } async function session() { const b = await chromium.connectOverCDP(`http://localhost:${CDP}`); const ctx = b.contexts()[0]; for (const p of ctx.pages()) await arm(p); ctx.on('page', arm); // new tabs // Broadcast-wide: when a tab enters HTML5 fullscreen (a video 'f'), hide // Chrome's toolbar by putting THAT tab's window into Chrome-initiated // fullscreen - xfwm4 won't hide it on HTML5 fullscreen alone, so the address // bar would otherwise show on the broadcast. We resolve the exact window of // the fullscreen tab (not just the first tab) and restore it on exit. const cdp = await b.newBrowserCDPSession(); let fsWindowId = null; const windowIdFor = async (page) => { const s = await page.context().newCDPSession(page); try { const { targetInfo } = await s.send('Target.getTargetInfo'); const { windowId } = await cdp.send('Browser.getWindowForTarget', { targetId: targetInfo.targetId }); return windowId; } finally { await s.detach().catch(() => {}); } }; const fsTimer = setInterval(async () => { try { let fsPage = null; for (const p of ctx.pages()) { if (await p.evaluate(() => !!document.fullscreenElement).catch(() => false)) { fsPage = p; break; } } if (fsPage && fsWindowId === null) { const windowId = await windowIdFor(fsPage); await cdp.send('Browser.setWindowBounds', { windowId, bounds: { windowState: 'fullscreen' } }); fsWindowId = windowId; } else if (!fsPage && fsWindowId !== null) { await cdp.send('Browser.setWindowBounds', { windowId: fsWindowId, bounds: { windowState: 'normal' } }); fsWindowId = null; } } catch { /* best-effort */ } }, 600); console.log('broadcast-helper armed on', ctx.pages().length, 'tab(s)'); await new Promise((resolve) => b.on('disconnected', resolve)); clearInterval(fsTimer); } // Reconnect across Chrome restarts so the broadcast stays ad-free. while (true) { try { await session(); } catch { /* CDP down */ } await new Promise((r) => setTimeout(r, 3000)); }