3 Player
jlightner edited this page 2026-04-04 06:55:06 -05:00

Web Media Player

Custom video player with HLS playback, synchronized transcript sidebar, and keyboard shortcuts. Added in M020/S01.

Architecture

┌──────────────────────────────────────────────────────────┐
│  WatchPage (/watch/:videoId)                             │
│                                                          │
│  ┌─────────────────────┐  ┌────────────────────────────┐ │
│  │    VideoPlayer       │  │   TranscriptSidebar        │ │
│  │  ┌────────────────┐  │  │  Segments sorted by time   │ │
│  │  │  <video> elem  │  │  │  Binary search for active  │ │
│  │  └────────────────┘  │  │  Auto-scroll on playback   │ │
│  │  PlayerControls      │  │  Click → seek to timestamp  │ │
│  └──────────┬───────────┘  └──────────┬─────────────────┘ │
│             │     useMediaSync()       │                   │
│             └──────────────────────────┘                   │
└──────────────────────────────────────────────────────────┘

Video Source Detection

Three-path fallback for maximum browser compatibility:

  1. HLS via hls.js — Lazy-loaded (dynamic import). Used when Hls.isSupported() returns true (Chrome, Firefox, Edge)
  2. Safari native HLS<video> element handles .m3u8 natively on Safari/iOS
  3. Direct MP4 fallback — Plain <video src="..."> for non-HLS sources

hls.js is loaded only when needed (~200KB), keeping the main bundle small.

Synchronized Transcript

The TranscriptSidebar component displays timestamped transcript segments synchronized to video playback:

  • Active segment detection: Binary search (O(log n)) on sorted segments by start_time
  • Auto-scroll: Active segment scrolls into view with scrollIntoView({ block: "center" })
  • Click to seek: Each segment is a <button> for keyboard accessibility. Click seeks the video to that timestamp
  • Non-blocking: Transcript fetch failure doesn't break the player — degrades gracefully

useMediaSync Hook

Shared playback state between VideoPlayer and TranscriptSidebar:

  • currentTime (number) — Current playback position in seconds
  • duration (number) — Total video duration
  • isPlaying (boolean) — Playback state
  • seekTo(time) (function) — Seek video to specific time

Keyboard Shortcuts

  • Space — Play/pause
  • ← / → — Seek ±5 seconds
  • Shift+← / Shift+→ — Seek ±10 seconds
  • ↑ / ↓ — Volume ±10%
  • M — Mute/unmute
  • F — Toggle fullscreen
  • 1-9 — Seek to 10%-90%

Speed Controls

Playback rate options: 0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x.

API Endpoints

  • GET /api/v1/videos/{id} — Video metadata with creator info (SourceVideoDetail)
  • GET /api/v1/videos/{id}/transcript — Ordered transcript segments (TranscriptForPlayerResponse)

Integration Points

  • TechniquePage: Key moment timestamps are clickable links to /watch/:videoId?t=<seconds>
  • Code-splitting: WatchPage is lazy-loaded via React.lazy + Suspense

Key Files

  • frontend/src/pages/WatchPage.tsx — Page component
  • frontend/src/components/VideoPlayer.tsx — Video element + HLS setup
  • frontend/src/components/PlayerControls.tsx — Play/pause, speed, volume, seek bar
  • frontend/src/components/TranscriptSidebar.tsx — Synchronized transcript display
  • frontend/src/components/AudioWaveform.tsx — Waveform visualization for audio content (M021/S05)
  • frontend/src/components/ChapterMarkers.tsx — Seek bar chapter overlay (M021/S05)
  • frontend/src/hooks/useMediaSync.ts — Shared playback state hook
  • backend/routers/videos.py — Video detail + transcript API

See also: Architecture, API-Surface, Frontend with chapter title

  • Click seeks playback to chapter start time
  • Integrated into PlayerControls via wrapper container div

Audio Waveform (M021/S05)

AudioWaveform component renders when content is audio-only (no video_url):

  • Hidden <audio> element shared between useMediaSync and WaveSurfer
  • wavesurfer.js with MediaElement backend — playback controlled identically to video mode
  • Dark-themed CSS matching the video player area
  • RegionsPlugin for labeled chapter regions with drag/resize support

Dependencies

  • wavesurfer.js — waveform rendering (~200KB, loaded only in audio mode)
  • useMediaSync hook widened from HTMLVideoElement to HTMLMediaElement for audio/video polymorphism

Key Moment Timeline Pins (M024/S02)

ChapterMarkers upgraded from thin 3px line ticks to 12px color-coded circle pins.

Pin Colors by Content Type

Content Type CSS Custom Property Color
technique --color-pin-technique Cyan
settings --color-pin-settings Amber
reasoning --color-pin-reasoning Purple
workflow --color-pin-workflow Green

Active State

When playback is within a key moment's time range, the corresponding pin scales to 1.3x. PlayerControls passes currentTime to ChapterMarkers for active detection.

Touch Targets

::before pseudo-element with inset:-6px provides touch-friendly hit areas without enlarging the visual pin.

Tooltips

Enriched: title + formatted time range + content type label.

Inline Player on Technique Pages (M024/S02)

Collapsible video player on TechniquePage, positioned between summary and body sections.

  • Collapse/expand animation — CSS grid-template-rows: 0fr/1fr transition
  • Multi-source-video selector — dropdown for technique pages sourced from multiple videos
  • Chapter pins — key moments rendered as pin markers on the inline seek bar
  • Bibliography seek wiring — time links render as seek buttons (calling mediaSync.seekTo) when the inline player is active for the matching video, or as <Link> to WatchPage otherwise
  • Uses existing useMediaSync hook, VideoPlayer, and PlayerControls components

Embed Player (M024/S03)

Chrome-free player at /embed/:videoId for iframe embedding.

  • No header/nav/footer — route registered at top-level Routes in App.tsx before AppShell catch-all
  • Content-type-aware height — video: 405px, audio-only: 120px
  • "Powered by Chrysopedia" branding — link opens origin in new tab with rel="noopener"
  • Dark full-viewport layout — matches existing player theme
  • Files: EmbedPlayer.tsx, EmbedPlayer.module.css

Key Files

  • frontend/src/pages/WatchPage.tsx — Page component
  • frontend/src/components/VideoPlayer.tsx — Video element + HLS setup
  • frontend/src/components/PlayerControls.tsx — Play/pause, speed, volume, seek bar, currentTime pass-through to ChapterMarkers
  • frontend/src/components/ChapterMarkers.tsx — 12px color-coded circle pins with active state and touch targets (M024/S02)
  • frontend/src/components/TranscriptSidebar.tsx — Synchronized transcript display
  • frontend/src/components/AudioWaveform.tsx — Waveform visualization for audio content (M021/S05)
  • frontend/src/pages/EmbedPlayer.tsx — Chrome-free embed player (M024/S03)
  • frontend/src/pages/TechniquePage.tsx — Inline collapsible player with bibliography seek (M024/S02)
  • frontend/src/hooks/useMediaSync.ts — Shared playback state hook
  • backend/routers/videos.py — Video detail + transcript API

See also: Architecture, API-Surface, Frontend