Copilot + CodeRabbit review findings:
- React.memo ChapterTicks and ChapterList (project guideline: hot-path
components must use React.memo to cut redraw work during control
updates).
- chapterNameAt now sorts the chapter array once instead of twice per
call. The previous version went through currentChapterIndex
(chapterStartsMs + sort) then sortedChapters (sort again). Runs on
every playback tick, so the duplicate work added up.
- Import getUserLibraryApi from the public barrel
(@jellyfin/sdk/lib/utils/api) instead of the deep internal path
(@jellyfin/sdk/lib/utils/api/user-library-api) to match the rest of
the codebase and avoid coupling to SDK file layout.
TrickplayBubble polish:
- Sit just above the slider (bottom: 0) so the bubble no longer overlaps
the progress bar.
- Move the chapter-name + timestamp overlay to the bottom-left of the
preview frame, smaller font, in front of the surrounding overlays
(zIndex + elevation).
BottomControls cleanup:
- Drop dev-only "pick one to test" comment in favour of a one-line note
on TICK_HEIGHT.
- Inline scrubMs into its useMemo callback so the scrub-chapter-name
lookup only recomputes while a slide is active.
Two presentational components, both player-agnostic:
ChapterTicks — absolute overlay that draws tick marks on the progress
slider, one per chapter start (skipping the leading 0ms marker).
- Reads markers from a memoized prop computed by the caller so the
filter/sort runs at most once per chapters change, not per render.
- Snaps tick position AND width to the device pixel grid via
PixelRatio.roundToNearestPixel(). Without this, fractional dp values
land at different sub-pixel fractions on non-integer density displays
(420dpi -> 2.625x ratio) and Android anti-aliases each tick
differently, making some look visibly thicker than others.
- Tick colour defaults to rgba(0,0,0,0.55), contrasting against both
the filled progress (#fff) and the unfilled track so ticks stay
visible as playback advances.
- pointerEvents="none" so the slider underneath still receives touches.
- overflow: "visible" so taller ticks can bleed past the parent track.
ChapterList — bottom-sheet modal listing chapters with their timestamps.
- Highlights the currently active row (purple primary tint).
- Falls back to a localized "Chapter N" label when a chapter has no name.
- Imperatively scrolls to the active row each time the sheet becomes
visible. <Modal> keeps its children mounted across visible toggles,
so FlatList.initialScrollIndex (which only fires at first mount) would
only work on the very first open. Uses a ref + useEffect on `visible`
+ scrollToIndex inside requestAnimationFrame, with an
onScrollToIndexFailed fallback for indices outside the render window.
- All static styles in StyleSheet.create() — only dynamic backgroundColor
/ text colour stays inline. The list re-renders on every playback tick
so cutting the per-render style allocations is worth it.
- Colors from constants/Colors.ts (primary, background, text, icon),
no hardcoded hex.