Replace the 3D ring jukebox with a flat curtain browser: selected season
faces the screen as top of the left stack, side cards rotate about their
outer edge and recede into the screen, each side evenly spaced with pitch
adapting to its card count so cards never leave the stage.
- Mount episode videos only after the season switch settles, then
preload=auto + autoplay (avoids mid-animation media setup jank)
- Start videos immediately (no stagger) once an episode is pointed at;
keep staggered starts while navigating the season selector
- Keyboard/gamepad entry into the season row lands on the selected
season via data-nav-entry-col instead of the x-closest card
- Black episode tiles; hidden (ahead-of-cursor) tiles get a sheen and
vignette veil that cross-fades on reveal/conceal
- Nudge season info panel 8px left for balanced gaps
Render only the selected season's episodes; switch via a perspective-arc
poster carousel with floating season info, deferred video loading with
still-image fade-in, and spoiler fade for episodes ahead of the cursor.
Skip ensure-visible and synced-row animations on focus restore, and
snapshot/restore panel scrollTop plus synced-row scrollLeft around
detail navigation so the browse page comes back pixel-identical.
- 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.