Commit Graph

12 Commits

Author SHA1 Message Date
KoalaDev 66971150c7 test: cover the popup blacklist UI, tighten audit findings
The delta model was only covered at module level; the popup wiring around
it was not exercised at all. Seven specs now drive the real settings UI in
the packed extension, including the migration path: a pre-v3.1.0 snapshot
is converted on open, the legacy key is removed, and a default missing
from that snapshot is delivered again.

Also from the audit pass:
- await the blacklist read in init instead of firing a floating promise
- unify the debug report on the finder's own candidate list, which the
  separate traversal missed shadow-DOM videos from
- assert that a single candidate is always returned regardless of its
  ranking signals, so no scoring signal can regress a single-player site

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:39:41 +02:00
KoalaDev 36e1291d2c feat: rank player candidates by ordered signals, add browser E2E suite
The weighted score summed incomparable units, so size could outvote traits
that disqualify an element outright. Measured on a real page: a display:none
preload reports its full 1080p intrinsic size and scored 2073600, beating a
visible unmuted player at 509920.

Selection now compares an ordered list of signals, highest priority first:
has a source, is rendered, is not a silent background loop, rendered size
bucket, is playing, has controls, duration. Rendered size replaces intrinsic
resolution, and mute state is gone from the ranking entirely: it is a viewer
preference, not evidence about which element is the player.

It stays a ranking rather than a filter, so a page of only bad candidates
still yields one and findVideo never returns null where a video exists.

The new tests/e2e suite runs the shipped finder against real fixture pages
and drives the packed extension for injection, reinjection and remote
play/pause/seek into a first-party frame. All five scoring scenarios fail
against the previous implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:23:12 +02:00
KoalaDev 809301b641 fix: detect videos inside same-origin player iframes
Sites like jkanime.net render the real <video> inside a first-party
iframe, so the top document had zero video elements and the content
script reported "NO VIDEO ELEMENT".

findVideo() now descends into reachable frame documents, the
MutationObserver registers those documents too (frame mutations never
bubble to the parent), and frame load events re-trigger the scan so a
late-loading player is still picked up. Debug reports count videos
across frames and expose an "In Iframe" flag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 06:36:07 +02:00
Timo 869c64171b Fix Disney+ force sync, seek, and HCM regressions for v2.5.3
The v2.5.2 Disney+ page-API integration leaked blob-relative <video>
time into force sync, seeks, and heartbeats when the page-API bridge
had no fresh data, so force sync on Disney+ appeared broken.

- getSyncCurrentTime/getSyncDuration now refuse native values on Disney+
  (return null/0) so stale bridge data degrades to a clean no-op instead
  of broadcasting garbage to peers. The get_current_time handler and
  episode/lobby/hcmIsLive paths are routed through the same accessor.
- Validate FORCE_SYNC_PREPARE/SEEK payloads as finite before relaying;
  the internal coercion no longer treats '' as 0.
- Stop double-routing FORCE_SYNC_PREPARE from the popup path (the
  generic popup route now covers only play/pause/seek).
- popup force-sync: exclude null/empty peer times from the jump-to-others
  median (Number(null)===0 was dragging the target to 0), guard against
  NaN end-to-end, clear the dangling reset timer on failure, and retry
  without re-injecting when the content script responds but the Disney+
  bridge has not yet delivered a finite time.
- hcmIsLive skips the native-duration live signal on Disney+ only,
  preserving YouTube/Twitch Infinity-duration live detection.

Disney-specific logic remains strictly gated to disneyplus.com; no
Netflix/YouTube/Twitch/generic path is affected.
2026-07-02 16:57:48 +02:00
Timo 9ac8c28442 Remove Disney+ DOM scraping fallback 2026-07-02 15:08:54 +02:00
Timo 076157fef1 Sync Disney+ via the page media player API (precise time + seek)
Disney+'s <video> is blob-relative (unusable as an absolute clock) and its
scrubber aria-value freezes during playback, so DOM scraping lagged and the
+/-10s button seek could neither reach far targets nor land precisely. The
real player hangs off the <disney-web-player> custom element as
`.mediaPlayer`, exposing seek(ms) and timeline.info (playhead/duration ms).

Since the isolated content world can't read that page object, route it
through the existing MAIN-world page-API bridge (as Netflix already does):

- page-api-seek-overrides.js: register a 'disney' provider.
- background.js installPageApiSeekBridge: seek Disney via mediaPlayer.seek(),
  and post the exact playhead/duration (seconds) to the content world every
  250ms. Both are gated on provider === 'disney'; Netflix path unchanged.
- content.js: cache the pushed playhead, prefer it in getDisneyPlusTimeline
  (DOM scraping stays as fallback), and check the page-API seek first in
  seekVideo. Outcome is identical for Netflix and generic sites.

Verified live on Disney+: reported time matches the player exactly and
seek lands within ~1s of the target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 14:56:02 +02:00
Timo 2fbcbc4f83 Harden Disney+ time fallback against <video> element recreation
When the control overlay (and its live time readout) is briefly absent,
the old fallback recomputed the timeline from the raw blob-relative
video.currentTime via a cached scale/start. Disney recreates the <video>
element on some play/pause transitions, which resets currentTime, so that
math produced a wildly wrong time ("loses the time" after a pause).

The native clock advances 1:1 with real playback, so instead anchor the
last live UI position to video.currentTime and extrapolate by the elapsed
native delta. If the delta is negative or implausibly large (element
recreated / seek), freeze at the last known position rather than emitting
a garbage time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 14:38:26 +02:00
Timo a9f5c0b9bf TEMPORARY DIAGNOSTICS: Integrate querySelectorAllShadow in Disney+ specific functions 2026-07-02 13:45:36 +02:00
Timo 32994aaaa5 Support dynamic Disney+ timeline offset/scale caching for accurate seek sync 2026-07-02 13:30:20 +02:00
Timo bea0f6925c Implement cached duration fallback and simulate keyboard events fallback for Disney+ seeks when controls are hidden 2026-07-02 13:27:53 +02:00
Timo 9cbeb661d6 Save baseline state with Disney+ seek adjustments and build identifier 2026-07-02 13:00:41 +02:00
Timo 101761e984 chore: add type:module, fix npm audit, rename CJS files, document module structure
- package.json: add type: module to silence ESM warnings
- server: npm audit fix (0 vulnerabilities, ws vuln resolved)
- Rename 4 CJS files to .cjs: build-extension, test-content-video-finder,
  test-locales, website/build
- Update eslint.config.mjs for .cjs glob patterns
- extension/README.md + server/README.md: document module structure
2026-06-16 13:22:45 +02:00