Skip to content

Immersive fullscreen player, touch-device perf, and edge caching - #17

Merged
harusharu merged 4 commits into
mainfrom
feat/immersive-player-and-edge-caching
Aug 20, 2026
Merged

Immersive fullscreen player, touch-device perf, and edge caching#17
harusharu merged 4 commits into
mainfrom
feat/immersive-player-and-edge-caching

Conversation

@harusharu

Copy link
Copy Markdown
Owner

Summary

This branch carries the next wave of the streaming UI upgrade: playback becomes a true immersive surface, the home screen gets faster on touch devices, and the API's hottest endpoints stop hammering upstream providers.

What's included

1. Immersive fullscreen player (feat(player))

  • Video now fills the viewport on black with all controls inside the stage; the player best-effort enters fullscreen and locks landscape on open, silently falling back to a tab-fill layout where browsers disallow it.
  • The old modal chrome (header, title row, side episode list) is removed — EpisodeList is deleted and episode switching lives in the player as an overlay panel.
  • In-video Settings panel with Quality / Speed / Audio / Subtitles tabs:
    • Quality & Audio are hub-based: one linkList entry per advertised resolution (480p/720p/1080p) or per audio language (multi-language WEB-DLs), falling back to manifest levels/tracks for plain HLS renditions.
    • Switching re-resolves the stream from that hub with failure memory (dead links skipped on retry); the winning hub is matched to preselect the active option; "Original" language leads the Audio menu.
  • External subtitle support: provider subtitle payloads (vtt/srt/ass/json, wildly inconsistent shapes) are normalized via subtitleTracksFrom and registered as Vidstack text tracks, with a dedicated Subtitles shortcut and no-stacking track switching.
  • Transport-cluster prev/next episode buttons (disabled at boundaries) + Shift+P / Shift+N shortcuts.
  • Safe-area-aware control bars and an auto-dismissing portrait-rotate hint.
  • DetailModal drops its old quality/season picker (superseded by the in-player settings) and gains dialog semantics: role="dialog", focus moved inside on open, Escape to close.

2. Touch-device performance (perf(ui))

  • New useIsFinePointer hook gates scroll-linked parallax, backdrop-filter, and infinite blur animations to fine pointers — the main Android scroll-jank sources are simply not mounted on touch.
  • Cards stop running getBoundingClientRect + spring updates per pointer-move during touch drags; spotlight/gradient/play-chip hover effects are hidden on coarse pointers.

3. API edge caching (perf(api))

  • The featured feed (hottest endpoint — every home load) gets an in-process single-flight 60s TTL cache plus Cache-Control: public, max-age=30, s-maxage=60, stale-while-revalidate=300, so bursts of users share one upstream fetch and Vercel's edge serves most loads without invoking the function.
  • The artwork proxy gets s-maxage=86400, stale-while-revalidate=604800 — browsers keep bytes for a day, the CDN for a week, keeping repeat loads off the proxy entirely.

4. Slimmer dock (ui(menu))

  • Compact dock: 32px icon circles, tightened padding, always-visible GitHub label, touch targets still well above the 44px floor.

Commits (grouped by concern)

Commit Scope
perf(api) cache featured feed in-process and tune CDN cache headers featured + image routes
perf(ui) drop parallax, blurs, and infinite animations on touch devices Hero, Card, useIsFinePointer
ui(menu) compact the horizontal dock FloatingMenu
feat(player) immersive fullscreen player with in-video audio, quality, and subtitles PlayerModal, PlayerStage, DetailModal, App wiring, types, reducer

Verification

  • pnpm lint (biome) — clean
  • pnpm typecheck (tsc --noEmit) — clean

@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
harustream Ready Ready Preview Aug 20, 2026 6:25am

The featured endpoint is the hottest route in the app — every home-screen
load hits it, and each hit previously re-fanned-out to every live provider
upstream. Two cache layers now absorb the load:

- An in-process single-flight TTL cache (60s) via cachedFetch so a burst
  of users can't stampede the upstream provider sites; in-flight requests
  share one upstream fetch.
- A CDN-friendly Cache-Control on the response
  ('public, max-age=30, s-maxage=60, stale-while-revalidate=300') so
  Vercel's edge serves the same feed to most users without invoking the
  function at all, and refreshes it in the background after expiry.

The artwork proxy route gains the same treatment with longer windows:
browsers keep the optimized bytes for a day while the CDN holds them for
a week (s-maxage=86400, stale-while-revalidate=604800), keeping
repeated loads entirely off the proxy since artwork rarely changes.
Scroll-linked transforms on a full-width, gradient-heavy layer are the
#1 Android scroll-jank source, and backdrop-filter + infinite blur
animations are nearly as bad on low-end phones. A new useIsFinePointer
hook (matchMedia '(pointer: fine)') now gates the expensive effects:

- Hero: the parallax wrapper (and its useScroll listener) is only
  mounted on fine pointers; touch devices render a static backdrop
  layer. Ambient orb drift, the Ken Burns settle, and the text shimmer
  are switched off via pointer-coarse:animate-none.
- Backdrop blur chips/pills fall back to solid backgrounds
  (pointer-coarse:bg-background/80, no backdrop-blur) on touch.
- Cards: the cursor spotlight, hover gradient scrim, and play chip skip
  pointer tracking entirely — a touch-drag no longer runs
  getBoundingClientRect + spring updates per move event.

SSR assumes a fine pointer and the effect corrects before first scroll,
so no hydration mismatch or flash.
Shrinks the dock's vertical stack so it sits lower on screen and reads
more like a slim dash: icon circles go from 36px to 32px with
proportionally smaller icons (size-4.5), button padding tightens
(pt-1.5 pb-0.5, gap-0.5), the container padding and divider height
follow, and the GitHub label is always visible instead of hidden on
mobile. Touch targets stay well above the 44px floor thanks to the
touch-target utility.
…y, and subtitles

Redesigns playback as a pure immersive surface — the video fills the
viewport on black with every control inside the stage, and the player
best-effort enters fullscreen and locks landscape on open (silently
falling back to the tab-fill layout where the browser disallows it).
The old modal chrome (header, title row, side episode list) is gone;
EpisodeList is deleted and the episode switcher now lives in the
player as a searchable overlay panel.

New in-video capabilities:
- Settings panel with Quality / Speed / Audio / Subtitles tabs.
  Quality and Audio lead with hub-based entries derived from the
  movie's linkList — one entry per advertised resolution
  (480p/720p/1080p) or per audio language (multi-language WEB-DLs) —
  and fall back to manifest levels/tracks when the stream is a plain
  HLS rendition. Switching re-resolves the stream from that hub with
  failure memory, so a dead link is skipped on retry; the winning
  hub is matched against the original stream to preselect the active
  option. Original language is tagged and leads the Audio menu.
- External caption registration: providers attach subtitle tracks
  (vtt/srt/ass/json) to stream sources; subtitleTracksFrom normalizes
  the wildly varying provider shapes and registers them as Vidstack
  text tracks, with a dedicated Subtitles shortcut button and
  no-stacking track switching.
- Transport-cluster prev/next episode buttons (disabled at list
  boundaries) plus Shift+P / Shift+N keyboard shortcuts.
- Safe-area aware top/bottom bars (env(safe-area-inset-*)) and a
  portrait rotate hint that auto-dismisses.

Wiring and state:
- App resolves and carries audioLanguages/hubQualities through the
  player/loading -> playing -> error cycle so menus stay populated
  while a switch is in flight, and re-derives them when a fallback
  hub takes over after sources are exhausted.
- Reducer player states carry the new optional audio/quality fields.
- DetailModal drops its old quality/season picker (superseded by the
  in-player settings) and gains dialog semantics: role=dialog,
  aria-modal, labelled by title, focus moved inside on open, Escape
  to close.
@harusharu
harusharu force-pushed the feat/immersive-player-and-edge-caching branch from 7f5ba79 to e3dafbe Compare August 20, 2026 06:24
@harusharu
harusharu merged commit 2999170 into main Aug 20, 2026
3 checks passed
@harusharu
harusharu deleted the feat/immersive-player-and-edge-caching branch August 20, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant