fix: cap selfbot stream -maxrate at lib's 10 Mbps ceiling; add stream-test tooling
- selfbot.ts: the @dank074 lib advertises a hardcoded max_bitrate of 10 Mbps to Discord (BaseMediaConnection: `max_bitrate: 10000 * 1000`). Our encoder used -maxrate = 1.5x target (12 Mbps at 8 Mbps target), so high-motion bursts exceeded the negotiated ceiling and WebRTC dropped packets (viewer stutter). Cap -maxrate at 10 Mbps. - Add bot/scripts/stream-test/: env-driven stream-hold.ts (persistent Go-Live holder), human.mjs (real xdotool mouse/keyboard + char-by-char typing), and scenario.mjs (YouTube/Naver browse). Channel/guild/video are env-parametrised. - .env.example: document DISCORD_VOICE_CHANNEL_ID for the stream-test scripts.
This commit is contained in:
77
bot/scripts/stream-test/scenario.mjs
Normal file
77
bot/scripts/stream-test/scenario.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
// Browse scenario driven with human-like real mouse/keyboard (see human.mjs).
|
||||
// Connects to a Chrome already running with --remote-debugging-port (default
|
||||
// 9222) on the streamed X display, and performs:
|
||||
// YouTube search -> open IU live concert -> 1080p -> watch 20s -> fullscreen
|
||||
// 20s -> Naver search 아이유 -> scroll -> 나무위키 -> scroll.
|
||||
//
|
||||
// Real input (visible on the stream): search/Naver typing, clicking the video,
|
||||
// the fullscreen button, scrolling, entering 나무위키.
|
||||
// API-driven (behind the scenes): window fullscreen, play, quality, autoplay
|
||||
// toggle, page navigation, and click fallbacks.
|
||||
import { chromium } from 'playwright';
|
||||
import { humanClick, humanType, pressKey, humanScroll, sleep } from './human.mjs';
|
||||
|
||||
const CDP = process.env.CDP_PORT || '9222';
|
||||
const VID = process.env.TEST_VIDEO_ID || 'X_am71G6Vy4'; // IU HEREH WORLD TOUR (live, 1080p+)
|
||||
const SEARCH = process.env.TEST_YT_QUERY || '내손을잡아';
|
||||
const NAVER_Q = process.env.TEST_NAVER_QUERY || '아이유';
|
||||
|
||||
const b = await chromium.connectOverCDP(`http://localhost:${CDP}`);
|
||||
const ctx = b.contexts()[0];
|
||||
const page = ctx.pages()[0];
|
||||
const s = await ctx.newCDPSession(page);
|
||||
page.setDefaultTimeout(25000);
|
||||
const winState = async (st) => { const { windowId } = await s.send('Browser.getWindowForTarget'); await s.send('Browser.setWindowBounds', { windowId, bounds: { windowState: st } }); };
|
||||
const ensurePlaying = () => page.evaluate(() => { const v = document.querySelector('video'); const p = document.getElementById('movie_player'); try { p && p.playVideo && p.playVideo(); } catch {} if (v && v.paused) v.play().catch(() => {}); });
|
||||
const autoplayOff = () => page.evaluate(() => { const btn = document.querySelector('.ytp-autonav-toggle-button'); if (btn && btn.getAttribute('aria-checked') === 'true') btn.click(); });
|
||||
|
||||
await page.evaluate(() => { if (document.fullscreenElement) document.exitFullscreen?.(); });
|
||||
await winState('normal');
|
||||
await page.goto('https://www.youtube.com', { waitUntil: 'domcontentloaded' }); await sleep(2500);
|
||||
|
||||
await humanClick(page, page.locator('input#search, input[name=search_query]').first());
|
||||
await humanType(SEARCH);
|
||||
await pressKey('Return');
|
||||
await sleep(3800);
|
||||
|
||||
let link = page.locator(`a#video-title[href*="${VID}"], a[href*="${VID}"]`).first();
|
||||
if (!(await link.count().catch(() => 0))) link = page.locator('ytd-video-renderer a#video-title, ytd-rich-item-renderer a#video-title').first();
|
||||
await humanClick(page, link);
|
||||
await sleep(3000);
|
||||
if (!/watch/.test(page.url())) await page.goto('https://www.youtube.com/watch?v=' + VID, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#movie_player', { timeout: 25000 }); await sleep(2500);
|
||||
for (let i = 0; i < 8; i++) { const ad = page.locator('.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button'); if (await ad.count().catch(() => 0)) { await ad.first().click({ timeout: 1500 }).catch(() => {}); await sleep(1200); } else break; }
|
||||
|
||||
await ensurePlaying(); await sleep(1200);
|
||||
await page.evaluate(() => { const p = document.getElementById('movie_player'); try { const L = p.getAvailableQualityLevels ? p.getAvailableQualityLevels() : []; const w = L.includes('hd1080') ? 'hd1080' : (L[0]); p.setPlaybackQualityRange && p.setPlaybackQualityRange(w, w); } catch {} });
|
||||
await autoplayOff();
|
||||
console.log('STEP watch-1080-windowed'); await sleep(20000);
|
||||
|
||||
await winState('fullscreen'); await sleep(1200);
|
||||
await humanClick(page, page.locator('.ytp-fullscreen-button'));
|
||||
await sleep(800); await ensurePlaying();
|
||||
console.log('STEP fullscreen'); await sleep(20000);
|
||||
|
||||
await page.evaluate(() => { if (document.fullscreenElement) document.exitFullscreen?.(); });
|
||||
await winState('normal'); await sleep(1500);
|
||||
|
||||
await page.goto('https://www.naver.com', { waitUntil: 'domcontentloaded' }); await sleep(2500);
|
||||
await humanClick(page, page.locator('input#query').first());
|
||||
await humanType(NAVER_Q);
|
||||
await pressKey('Return');
|
||||
await sleep(2800);
|
||||
await humanScroll(page, +1, 18);
|
||||
console.log('STEP naver-scrolled');
|
||||
|
||||
const namu = page.locator('a[href*="namu.wiki"]').first();
|
||||
if (await namu.count().catch(() => 0)) {
|
||||
await humanClick(page, namu);
|
||||
await sleep(3000);
|
||||
await humanScroll(page, +1, 14);
|
||||
await humanScroll(page, -1, 8);
|
||||
await humanScroll(page, +1, 10);
|
||||
console.log('STEP namu-scrolled');
|
||||
} else console.log('STEP namu-not-found');
|
||||
|
||||
console.log('SCENARIO_DONE');
|
||||
await b.close();
|
||||
Reference in New Issue
Block a user