channel-worker 2.5.26 → 2.5.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.26",
3
+ "version": "2.5.28",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {
@@ -114,36 +114,34 @@ async function collectResultVideoUrls(page) {
114
114
  // skippable ad; non-skippable ads have no button (we wait them out). Multiple
115
115
  // pre-rolls can chain, so this is called every tick of the watch loop.
116
116
  async function trySkipAd(page) {
117
- return page.evaluate(() => {
118
- const sels = [
119
- '.ytp-ad-skip-button-modern',
120
- '.ytp-ad-skip-button',
121
- '.ytp-skip-ad-button',
122
- 'button.ytp-ad-skip-button-modern',
123
- '.ytp-ad-skip-button-container button',
124
- ];
125
- for (const s of sels) {
126
- const btn = document.querySelector(s);
127
- if (btn && btn.offsetParent !== null) { btn.click(); return true; }
128
- }
129
- // Text-based fallback (locale-proof) — any visible button whose label is a
130
- // skip-ad phrase.
131
- const phrases = /bỏ qua quảng cáo|bỏ qua|skip ad|skip ads|skip/i;
132
- for (const b of document.querySelectorAll('button, .ytp-ad-skip-button-text')) {
133
- const t = (b.innerText || b.textContent || '').trim();
134
- if (t && phrases.test(t) && b.offsetParent !== null) { b.click(); return true; }
117
+ // Use Playwright's real .click() (trusted pointer event) a synthetic
118
+ // el.click() from page.evaluate is ignored by YouTube's player, so the ad
119
+ // never actually skipped (saw 52 "skips" with the ad still playing). The
120
+ // skip button only becomes clickable ~5s into the ad; an early click times
121
+ // out → return false → retried next tick.
122
+ const sels = [
123
+ '.ytp-ad-skip-button-modern',
124
+ '.ytp-ad-skip-button',
125
+ '.ytp-skip-ad-button',
126
+ '.ytp-ad-skip-button-container button',
127
+ ];
128
+ for (const s of sels) {
129
+ const loc = page.locator(s).first();
130
+ if (await loc.isVisible().catch(() => false)) {
131
+ try { await loc.click({ timeout: 1500 }); return true; } catch { /* not yet clickable */ }
135
132
  }
136
- return false;
137
- }).catch(() => false);
133
+ }
134
+ return false;
138
135
  }
139
136
 
140
- // True while an ad is playing on the watch page (pre/mid-roll). Used so the
141
- // watch budget only counts REAL video time, not ad time.
137
+ // True while an ad is ACTUALLY playing (pre/mid-roll). Relies on the player's
138
+ // `ad-showing` class the canonical signal YouTube toggles only during ad
139
+ // playback. Do NOT key off .ytp-ad-module (a persistent container that exists
140
+ // even with no ad → false positive that pinned the watch loop at "0s real").
142
141
  async function isAdShowing(page) {
143
142
  return page.evaluate(() => {
144
143
  const player = document.querySelector('#movie_player, .html5-video-player');
145
- if (player && player.classList.contains('ad-showing')) return true;
146
- return !!document.querySelector('.ytp-ad-player-overlay, .ytp-ad-module, .ad-showing');
144
+ return !!(player && player.classList.contains('ad-showing'));
147
145
  }).catch(() => false);
148
146
  }
149
147
 
@@ -165,7 +163,7 @@ async function watchVideo(page, url, minSec, maxSec, log) {
165
163
  log('info', `[warmup] watching ${url.slice(-11)} for ~${Math.round(watchMs / 1000)}s of REAL video (skipping ads)`);
166
164
 
167
165
  const TICK = 1000;
168
- const MAX_AD_WAIT_MS = 90_000; // cap total ad-waiting so a non-skippable ad reel can't hang the session
166
+ const MAX_AD_WAIT_MS = 60_000; // cap total ad-waiting so a non-skippable ad reel can't hang the session
169
167
  let realWatched = 0; // ms of actual (non-ad) playback counted
170
168
  let adWaited = 0; // ms spent sitting through/again skipping ads
171
169
  let scrolled = false;
@@ -185,7 +183,8 @@ async function watchVideo(page, url, minSec, maxSec, log) {
185
183
  await page.waitForTimeout(TICK);
186
184
  }
187
185
  if (skips) log('info', `[warmup] ${url.slice(-11)} — skipped ${skips} ad tick(s), real watch ${Math.round(realWatched/1000)}s`);
188
- return true;
186
+ // Only count as a watched video if real (non-ad) content actually played.
187
+ return realWatched > 0;
189
188
  } catch (e) {
190
189
  log('warn', `[warmup] watch failed (${url.slice(-11)}): ${String(e.message || e).slice(0, 100)}`);
191
190
  return false;