System Intent
- What this is: A shared React hook (
main/app/lib/hooks/useMediaPlayer.ts) that centralises playback state management — play/pause, position, duration, mute, and controls-overlay visibility — for both AudioPlayer and VideoPlayer components. It provides stable callbacks for toggling play, mute, scrubbing, and handling tap-to-toggle controls, plus a showControlsBriefly helper that auto-hides the overlay after 3 s. An optional onStateChange callback fires whenever any state value changes.
Mermaid Diagram
flowchart TD
Caller[AudioPlayer / VideoPlayer] -->|useMediaPlayer options| Hook[useMediaPlayer]
Hook -->|returns state| Caller
Caller -->|togglePlay| Hook
Hook -->|setIsPlaying toggle| State[isPlaying / position / duration / isMuted / controlsVisible]
Caller -->|handleMediaTap| Hook
Hook -->|setControlsVisible + setTimeout 3s| AutoHide[controlsVisible = false]
Caller -->|toggleMute| Hook
Hook -->|setIsMuted toggle| State
Caller -->|handleScrub ratio| Hook
Hook -->|setPosition ratio*duration| State
Flows
- Core files:
main/app/lib/hooks/useMediaPlayer.ts - Test files: none currently
Types
MediaPlayerState {
isPlaying: boolean
position: number (seconds)
duration: number (seconds)
isMuted: boolean
controlsVisible: boolean
}
UseMediaPlayerOptions {
duration?: number (initial duration, default 30)
onStateChange?: (state: MediaPlayerState) => void
}
// Return value includes all MediaPlayerState fields plus:
setIsPlaying: (v: boolean) => void
setPosition: (v: number) => void
setDuration: (v: number) => void
setIsMuted: (v: boolean) => void
setControlsVisible: (v: boolean) => void
togglePlay: () => void
toggleMute: () => void
handleScrub: (ratio: number) => void // ratio in [0, 1]; clamped internally
handleMediaTap: () => void
showControlsBriefly: () => void
Paths
| path | input | output | path-type | notes |
mediaPlayerState.togglePlay | call togglePlay() | isPlaying flipped; showControlsBriefly fired | happy path | |
mediaPlayerState.toggleMute | call toggleMute() | isMuted flipped; showControlsBriefly fired | happy path | |
mediaPlayerState.scrub | handleScrub(ratio) | position = clamp(ratio,0,1)*duration; showControlsBriefly | happy path | ratio clamped to [0,1] |
mediaPlayerState.tapShow | handleMediaTap() while hidden | controlsVisible = true; auto-hide timer set (3 s) | happy path | |
mediaPlayerState.tapHide | handleMediaTap() while visible | controlsVisible = false; existing timer cleared | happy path | |
mediaPlayerState.showBriefly | showControlsBriefly() | controlsVisible = true; any prior timer cancelled; new 3 s timer set | happy path | |
mediaPlayerState.stateChange | any state change | onStateChange called with current snapshot | happy path | only fires if onStateChange provided |
mediaPlayerState.unmount | component unmounts | pending auto-hide timer cleared | happy path | prevents setState on unmounted component |
Pseudocode
showControlsBriefly():
setControlsVisible(true)
clearTimeout(controlsTimerRef)
controlsTimerRef = setTimeout(() => setControlsVisible(false), 3000)
handleMediaTap():
setControlsVisible(prev =>
if prev: // hiding
clearTimeout(controlsTimerRef); return false
else: // showing
clearTimeout + set new 3s timer; return true
)
handleScrub(ratio):
seconds = clamp(ratio, 0, 1) * duration
setPosition(seconds)
showControlsBriefly()
useEffect cleanup:
clearTimeout(controlsTimerRef)
Logs
| Source | Location |
| React Native | Metro / device console |
Deployment
- Mechanism:
local only - Deploy command:
cd main/app && npx expo start
- Notes: Pure React hook — no native dependencies. Consumed by
AudioPlayer and VideoPlayer (inside MemoryViewerModal).