5.6 KiB
Chromecast Player Split — Design
Date: 2026-05-22
Branch: refactor-chromecast (PR #1402)
Sub-project: C of the Chromecast refactor
Status: Approved design — pending implementation plan
1. Problem
app/(auth)/casting-player.tsx is a 1428-line god-component. One file owns: cast SDK
hooks, item fetching, live-progress tracking, slider/scrubbing, trickplay, track
selection, episode/season fetching, segment skipping, the dismiss gesture, and ~780
lines of JSX. It is hard to read, hard to change safely, and every sub-project so far
has had to navigate it.
2. Scope
In scope: a purely structural decomposition of casting-player.tsx into 4 custom
hooks, 6 presentational components, and a thin orchestrator.
Out of scope — and a hard constraint: zero behaviour change. This is a
mechanical extraction. No bug fixes, no feature changes, no logic edits. Known issues
in this code (the loadEpisode / currentItem race from sub-project A's review; the
trickplay window truncation, the progress-bar touch overlap, the time-label position)
are explicitly NOT touched here — they belong to a later UX sub-project, and are
easier to fix once the code lives in small focused files.
3. Architecture
casting-player.tsx becomes an orchestrator (~150-200 lines): it calls the hooks,
assembles the data, and renders the 6 components plus the 3 existing modal components.
State flows orchestrator → components by props (approved approach 1) — no React context. Presentational components take typed props; all logic lives in hooks.
4. Hooks (hooks/)
| Hook | Extracts | Returns (shape) |
|---|---|---|
useCastPlayerItem |
fetchedItem state, its fetch effect, the currentItem derivation |
{ fetchedItem, currentItem } |
useCastPlayerProgress |
slider shared values, isScrubbing, trickplayTime, scrubPercentage, liveProgress, the sync refs/effects, the useTrickplay call |
slider state, scrub handlers, progress, trickplay data |
useCastEpisodes |
episodes / nextEpisode / seasonData state, their fetch effects, loadEpisode |
{ episodes, nextEpisode, seasonData, loadEpisode } |
useCastDismissGesture |
translateY, context, panGesture, dismissModal, animatedStyle |
{ panGesture, animatedStyle, dismissModal } |
Existing hooks are reused unchanged: useCasting, useCastSelection,
useChromecastSegments, useTrickplay (the latter called inside
useCastPlayerProgress).
useCastPlayerProgress is the most intricate hook (shared values, refs, the
live-progress interpolation) — it must be extracted with care and reviewed closely.
5. Components (components/casting/player/)
| Component | Extracts (JSX section) |
|---|---|
CastPlayerHeader |
dismiss chevron, connection indicator, settings button |
CastPlayerTitle |
title + episode/season info |
CastPlayerPoster |
poster image, buffering overlay, skip intro/credits bar |
CastPlayerEpisodeControls |
the 4-button row (Episodes / Previous / Next / Stop) |
CastPlayerProgressBar |
slider, trickplay preview, time display |
CastPlayerTransportControls |
rewind / play-pause / forward |
Each is a pure presentational component with typed props. The 3 modal components
(ChromecastDeviceSheet, ChromecastEpisodeList, ChromecastSettingsMenu) already
exist and stay as-is — the orchestrator keeps rendering them.
6. Constraints
- Mechanical extraction only. Each component's / hook's props and returns mirror exactly what the inline code used. No new logic, no renamed behaviour, no fixes.
- Component prop interfaces are derived from what the extracted JSX references in the original component scope.
- Follow existing repo conventions: hooks flat in
hooks/; the new components grouped undercomponents/casting/player/.
7. Verification
The casting UI has no unit tests (consistent with the rest of the casting UI — it is React Native / SDK-heavy). Verification is:
bun run typecheck— green after every task (catches wiring/prop errors).bun test utils/casting/— stays green (the pure-logic suites are untouched).- A full manual re-test of the cast player after the split: cast a movie and an episode, switch audio / subtitle / quality / version, navigate episodes, scrub the progress bar, trigger trickplay, dismiss the player. Behaviour must be identical to before the split.
8. Files
Created:
hooks/useCastPlayerItem.ts,hooks/useCastPlayerProgress.ts,hooks/useCastEpisodes.ts,hooks/useCastDismissGesture.tscomponents/casting/player/CastPlayerHeader.tsx,CastPlayerTitle.tsx,CastPlayerPoster.tsx,CastPlayerEpisodeControls.tsx,CastPlayerProgressBar.tsx,CastPlayerTransportControls.tsx
Modified:
app/(auth)/casting-player.tsx→ thin orchestrator.
9. Success criteria
casting-player.tsxis ~150-200 lines and contains no inline logic clusters or large JSX sections — only hook calls and component composition.- Each new file has one clear responsibility and a typed interface.
bun run typecheckandbun test utils/casting/pass.- The manual re-test shows behaviour identical to before the split.
10. Risks
- The hooks share state (progress feeds the slider, the time display, and segment
skipping). The extraction must preserve the exact data flow and effect ordering.
Mitigation: one hook / component per task,
bun run typecheckafter each, and a full manual re-test at the end. useCastPlayerProgresscarries reanimated shared values and timing refs — the highest-risk extraction; its task gets the closest review.- No unit tests guard the UI — the manual re-test is the only behavioural safety net.