- Observe the video element itself instead of el.closest('.episode-tile'):
ref callbacks can fire before ancestors are attached, so no tile was
ever observed and the visibility allowlist blocked all playback.
- Use a blocklist for off-screen culling (play until reported off-screen)
so observer lag/failure degrades to the old always-on behavior.
- Default to the first season playing on page view again.
- Keep preload="auto" so tiles show a first-frame preview picture;
playback gating already prevents GPU use from inactive videos.
- Stop videos staggered (same 500ms step as startup) on idle stop,
season switch and scroll culling; same for movie page showreels.
- Add useIdlePreviewPlayback composable: stops preview videos after 30s
without input and restarts them staggered on activity; also stops
while the tab is hidden.
- SeriesFullView: no episode videos play until a season is browsed
(keyboard/gamepad focus or mouse hover); only near-viewport tiles of
the active season play (IntersectionObserver); inactive seasons no
longer preload video data; sync is incremental so playing tiles are
not rewound on scroll.
- MediaDetail: header showreel videos use cancellable stagger timers,
pause when scrolled out of view, and honor the idle stop.
- Images: lazy/async decoding on series page posters; content-visibility:
auto on media cards to skip rendering off-screen row items.
Backend:
- Add mediahive/players.py with cross-platform player detection:
- Windows: MPC-BE, MPC-HC, VLC, PotPlayer, mpv (registry + known paths)
- macOS: IINA, VLC (/Applications bundle scanning)
- Linux: VLC, SMPlayer (PATH via which)
- Add GET /api/players endpoint returning detected players
- Extend POST /api/roots/{id}/play to accept player_id and player_custom_cmd
- Make MPC-BE web UI port configurable via ?port= query param
Frontend:
- Add player selection to settings (localStorage, per-client)
- Two-column layout: player list (9.5em) + contextual options panel
- Contextual options: Custom Command input, MPC Web UI Port input
- Empty MPC port disables all Web UI polling/connection attempts
- MPC-BE polling only runs when MPC family player is selected and port is set
- useMediaWebSocket: shallowRef mediaIndex/tasks/loading/error/connected
to eliminate Proxy overhead on the entire library data structure.
- useMediaWebSocket: replace per-task setTimeout leak with single 3s
sweep interval for completed task cleanup.
- App.vue: remove {deep:true} watcher on mediaIndex; shallow ref change
is sufficient to trigger search worker sync.
- useGamepadNavigation: rAF only when gamepads are connected; idle
fallback to 500ms setTimeout to stop permanent 60fps CPU drain.
- useKeyboardNavigation: deduplicate synced scroll rAF requests to
prevent overlapping animation frames.
- MediaDetail/SeriesFullView/CollageHero: pause, clear src, and load()
video elements on unmount and before ref replacement to release
decoder/memory resources.
- MediaDetail/SeriesFullView: clear all volume fade intervals on
unmount to stop interval timer leaks.
Replace the fragile per-row tail/maxVirtual state with a clean global-
metrics approach:
- Measure cardWidth, stride, paddingLeft, viewportWidth, rightDeadzone
once from the first synced row; use the same values for all rows.
- Per-row max scroll computed purely from math:
max(0, paddingLeft + (N-1)*stride - maxVisibleLeft)
where maxVisibleLeft = viewportWidth - cardWidth - rightDeadzone.
- Global virtual offset (syncedRowsCurrentOffset/targetOffset) is shared
across all rows; each row clamps independently in applySyncedRowScroll.
- Tail is a global constant (= rightDeadzone) applied to all rows.
- Fix scrolling-back bug: desiredOffset was stuck at currentOffset when
moving left; now always computed from anchor column position.
- Avoid full animation sweep on view entry: init current/target offset
from existing DOM scrollLeft on first use.
Also removes per-row --sync-row-max-virtual CSS var and simplifies
.media-row padding to use only --sync-row-tail.
Remove the separate Play and Info buttons from the CollageHero featured
item. All collage items (including the featured one) are now uniform
<a href> links that navigate to the detail page.
- CollageHero: remove hasResumePosition prop, play/info emits, and
associated play button logic
- App.vue: remove @play and @info bindings from CollageHero
- Delete unused HeroSection.vue component and its styles
MediaCard, MediaRow, CollageHero, HeroSection, and MediaDetail cast
cards now render as <a> tags with real hrefs. Modified clicks
(ctrl+click, middle-click) navigate natively, enabling open-in-new-tab
and link copying. Plain left-clicks continue through the existing
showDetail flow for side-effects like focus-state saving.
The clip-path slant on collage slices previously varied with the number
of posters (hard-coded 10% steps). Express the slant as a percentage
of each slice's own width so the horizontal offset always equals the
fixed 5% container overlap, giving a constant angle for any count >= 2.