Commit Graph

2 Commits

Author SHA1 Message Date
Gauvain
e3f4eea132 fix(chapters): address review findings + trickplay polish
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.
2026-05-27 20:08:21 +02:00
Gauvain
5f64ce49c0 feat(chapters): add ChapterTicks overlay and ChapterList modal
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.
2026-05-27 16:39:32 +02:00