@@ -96,6 +96,93 @@ export function Player(props?: {
9696
9797 const playerRef = useRef < PlayerRef > ( null ) ;
9898 const lastTaskIdRef = useRef < string | null > ( null ) ;
99+ const wrapperRef = useRef < HTMLDivElement > ( null ) ;
100+ const markerHoverIntervalRef = useRef < ReturnType < typeof setInterval > | null > (
101+ null ,
102+ ) ;
103+
104+ // Override Remotion Player seek bar styles via DOM
105+ useEffect ( ( ) => {
106+ const node = ( playerRef . current as any ) ?. getContainerNode ?.( ) ;
107+ if ( ! node ) return ;
108+
109+ const applySeekBarStyles = ( ) => {
110+ // Find seek bar container (has cursor: pointer and touch-action: none)
111+ const seekBarContainer = node . querySelector (
112+ '[style*="touch-action"]' ,
113+ ) as HTMLElement | null ;
114+ if ( ! seekBarContainer ) return ;
115+
116+ // Bar background: first child of seek bar container
117+ const barBg = seekBarContainer . firstElementChild as HTMLElement | null ;
118+ if ( barBg ) {
119+ barBg . style . setProperty (
120+ 'background-color' ,
121+ 'rgba(255, 255, 255, 0.3)' ,
122+ 'important' ,
123+ ) ;
124+ }
125+
126+ // Fill bar: last child of bar background
127+ const fillBar = barBg ?. lastElementChild as HTMLElement | null ;
128+ if ( fillBar ) {
129+ fillBar . style . setProperty (
130+ 'background-color' ,
131+ 'rgba(43, 131, 255, 1)' ,
132+ 'important' ,
133+ ) ;
134+ }
135+
136+ // Knob: last child of seek bar container
137+ const knob = seekBarContainer . lastElementChild as HTMLElement | null ;
138+ if ( knob && knob !== barBg ) {
139+ knob . style . setProperty ( 'opacity' , '1' , 'important' ) ;
140+ knob . style . setProperty ( 'background-color' , '#fff' , 'important' ) ;
141+ knob . style . setProperty ( 'box-shadow' , 'none' , 'important' ) ;
142+ knob . style . setProperty ( 'width' , '8px' , 'important' ) ;
143+ knob . style . setProperty ( 'height' , '16px' , 'important' ) ;
144+ knob . style . setProperty ( 'border-radius' , '10px' , 'important' ) ;
145+ // Vertically center: bar center at 6.5px from container top, knob height 16
146+ knob . style . setProperty ( 'top' , '-1.5px' , 'important' ) ;
147+ }
148+
149+ // Sync chapter markers visibility with control bar
150+ const controlBar = node . querySelector (
151+ '[style*="transition"]' ,
152+ ) as HTMLElement | null ;
153+ const markersEl = wrapperRef . current ?. querySelector (
154+ '.chapter-markers' ,
155+ ) as HTMLElement | null ;
156+ if ( controlBar && markersEl ) {
157+ markersEl . style . opacity = window . getComputedStyle ( controlBar ) . opacity ;
158+ }
159+ } ;
160+
161+ // Apply initially and observe for re-renders
162+ const timer = setInterval ( applySeekBarStyles , 300 ) ;
163+ applySeekBarStyles ( ) ;
164+ return ( ) => clearInterval ( timer ) ;
165+ } , [ frameMap ] ) ;
166+
167+ // Continuously dispatch mousemove on Remotion Player root to keep controls visible
168+ // Remotion uses mouseenter/mouseleave/mousemove to track hover state,
169+ // and hides controls after a timeout with no mouse activity
170+ const onMarkerMouseEnter = useCallback ( ( ) => {
171+ const node = ( playerRef . current as any ) ?. getContainerNode ?.( ) ;
172+ if ( ! node ) return ;
173+ const dispatchMove = ( ) => {
174+ node . dispatchEvent ( new MouseEvent ( 'mouseenter' , { bubbles : false } ) ) ;
175+ node . dispatchEvent ( new MouseEvent ( 'mousemove' , { bubbles : true } ) ) ;
176+ } ;
177+ dispatchMove ( ) ;
178+ markerHoverIntervalRef . current = setInterval ( dispatchMove , 200 ) ;
179+ } , [ ] ) ;
180+ const onMarkerMouseLeave = useCallback ( ( ) => {
181+ if ( markerHoverIntervalRef . current ) {
182+ clearInterval ( markerHoverIntervalRef . current ) ;
183+ markerHoverIntervalRef . current = null ;
184+ }
185+ } , [ ] ) ;
99186
100187 // Track frame for taskId callback
101188 useEffect ( ( ) => {
@@ -391,7 +478,7 @@ export function Player(props?: {
391478 return (
392479 < div className = "player-container" data-fit-mode = { props ?. fitMode } >
393480 < div className = "canvas-container" >
394- < div className = "player-wrapper" >
481+ < div className = "player-wrapper" ref = { wrapperRef } >
395482 < RemotionPlayer
396483 ref = { playerRef }
397484 component = { Composition }
@@ -423,10 +510,16 @@ export function Player(props?: {
423510 { chapterMarkers . length > 0 && (
424511 < div className = "chapter-markers" >
425512 { chapterMarkers . map ( ( marker ) => (
426- < Tooltip key = { marker . percent } title = { marker . title } >
513+ < Tooltip
514+ key = { marker . percent }
515+ title = { marker . title }
516+ overlayClassName = "chapter-tooltip"
517+ >
427518 < div
428519 className = "chapter-marker"
429520 style = { { left : `${ marker . percent } %` } }
521+ onMouseEnter = { onMarkerMouseEnter }
522+ onMouseLeave = { onMarkerMouseLeave }
430523 onClick = { ( ) => {
431524 playerRef . current ?. seekTo ( marker . frame ) ;
432525 } }
0 commit comments