@@ -48,23 +48,37 @@ export function Timeline({
4848
4949 const progressRef = useRef < HTMLDivElement | null > ( null ) ;
5050
51+ const safeTotalTime = Number . isFinite ( totalTime ) && totalTime > 0 ? totalTime : 0 ;
52+ const getBoundedTime = ( time : number ) : number => {
53+ if ( ! Number . isFinite ( time ) || safeTotalTime === 0 ) {
54+ return 0 ;
55+ }
56+
57+ return clamp ( time , 0 , safeTotalTime ) ;
58+ } ;
59+ const getProgressPercent = ( time : number ) : number => safeTotalTime === 0 ? 0 : getBoundedTime ( time ) / safeTotalTime * 100 ;
60+
5161 const getTimeFromMouseEvent = ( e : MouseEvent | React . MouseEvent ) : number => {
52- if ( ! progressRef . current || totalTime === 0 ) {
62+ if ( ! progressRef . current || safeTotalTime === 0 ) {
5363 return 0 ;
5464 }
5565
5666 const rect = progressRef . current . getBoundingClientRect ( ) ;
67+ if ( rect . width <= 0 ) {
68+ return 0 ;
69+ }
70+
5771 const offsetX = e . clientX - rect . left ;
5872 const ratio = Math . max ( 0 , Math . min ( 1 , offsetX / rect . width ) ) ;
5973
60- return ratio * totalTime ;
74+ return ratio * safeTotalTime ;
6175 } ;
6276
6377 useEffect ( ( ) => {
6478 if ( ! isScrubbing && ! isHovering ) {
65- setDisplayTime ( currentTime ) ;
79+ setDisplayTime ( getBoundedTime ( currentTime ) ) ;
6680 }
67- } , [ currentTime , isScrubbing , isHovering ] ) ;
81+ } , [ currentTime , isScrubbing , isHovering , safeTotalTime ] ) ;
6882
6983 // Disable text selection while scrubbing
7084 useEffect ( ( ) => {
@@ -118,7 +132,7 @@ export function Timeline({
118132 document . removeEventListener ( 'mousemove' , handleMouseMove ) ;
119133 document . removeEventListener ( 'mouseup' , handleMouseUp ) ;
120134 } ;
121- } , [ isScrubbing , totalTime ] ) ;
135+ } , [ isScrubbing , safeTotalTime ] ) ;
122136
123137 const onTimelineMouseDown = ( e : React . MouseEvent ) : void => {
124138 const clickedTime = getTimeFromMouseEvent ( e ) ;
@@ -147,17 +161,23 @@ export function Timeline({
147161 setIsHovering ( false ) ;
148162 // If not scrubbing, revert the knob to real player time
149163 if ( ! isScrubbing ) {
150- setDisplayTime ( currentTime ) ;
164+ setDisplayTime ( getBoundedTime ( currentTime ) ) ;
151165 }
152166 onMouseLeave ?.( ) ;
153167 } ;
154168
155- const highlightStartTime = highlightState . highlightStartTime - playerStartTimestamp ;
156- const highlightEndTime = highlightState . highlightEndTime - playerStartTimestamp ;
157- const progressPercent = clamp ( ( ( isHovering && ! isScrubbing ? currentTime : displayTime ) / totalTime ) * 100 , 0 , 100 ) * Number ( ! isSnapshotMissing ) ;
158- const highlightRegionWidth = highlightState . isActive ? clamp ( ( highlightEndTime - highlightStartTime ) / totalTime * 100 , 0 , 100 ) * Number ( ! isSnapshotMissing ) : 0 ;
159- const leftKnobPosition = clamp ( ( ( highlightState . isActive ? highlightStartTime : displayTime ) / totalTime ) * 100 , 0 , 100 ) * Number ( ! isSnapshotMissing ) ;
160- const rightKnobPosition = clamp ( ( ( highlightState . isActive ? highlightEndTime : displayTime ) / totalTime ) * 100 , 0 , 100 ) * Number ( ! isSnapshotMissing ) ;
169+ const rawHighlightStartTime = highlightState . highlightStartTime - playerStartTimestamp ;
170+ const rawHighlightEndTime = highlightState . highlightEndTime - playerStartTimestamp ;
171+ const highlightStartTime = getBoundedTime ( Math . min ( rawHighlightStartTime , rawHighlightEndTime ) ) ;
172+ const highlightEndTime = getBoundedTime ( Math . max ( rawHighlightStartTime , rawHighlightEndTime ) ) ;
173+ const visibleTimelineMultiplier = Number ( ! isSnapshotMissing ) ;
174+ const progressPercent = getProgressPercent ( isHovering && ! isScrubbing ? currentTime : displayTime ) * visibleTimelineMultiplier ;
175+ const highlightStartPercent = getProgressPercent ( highlightStartTime ) ;
176+ const highlightEndPercent = getProgressPercent ( highlightEndTime ) ;
177+ const highlightRegionWidth = highlightState . isActive ? Math . max ( highlightEndPercent - highlightStartPercent , 0 ) * visibleTimelineMultiplier : 0 ;
178+ const leftKnobPosition = ( highlightState . isActive ? highlightStartPercent : getProgressPercent ( displayTime ) ) * visibleTimelineMultiplier ;
179+ const rightKnobPosition = ( highlightState . isActive ? highlightEndPercent : getProgressPercent ( displayTime ) ) * visibleTimelineMultiplier ;
180+ const loadingProgressPercent = ( Number . isFinite ( downloadProgress ) ? clamp ( downloadProgress , 0 , 100 ) : 0 ) * visibleTimelineMultiplier ;
161181
162182 const containerClasses = classNames (
163183 styles . container ,
@@ -175,7 +195,7 @@ export function Timeline({
175195 return (
176196 < div className = { containerClasses } >
177197 < div className = { styles . playerTime } >
178- { isSnapshotMissing ? '––:––' : formatTime ( displayTime ) }
198+ { isSnapshotMissing ? '––:––' : formatTime ( getBoundedTime ( displayTime ) ) }
179199 </ div >
180200
181201 < div
@@ -197,7 +217,7 @@ export function Timeline({
197217 < div
198218 className = { styles . playerProgress }
199219 style = { {
200- width : `${ ( isLoading ? downloadProgress : progressPercent ) . toFixed ( 2 ) } %`
220+ width : `${ ( isLoading ? loadingProgressPercent : progressPercent ) . toFixed ( 2 ) } %`
201221 } }
202222 >
203223 < div className = { styles . progressPulse } />
@@ -237,7 +257,7 @@ export function Timeline({
237257 </ div >
238258
239259 < div className = { styles . playerTime } >
240- { isSnapshotMissing ? '––:––' : formatTime ( totalTime ) }
260+ { isSnapshotMissing ? '––:––' : formatTime ( safeTotalTime ) }
241261 </ div >
242262 </ div >
243263 ) ;
0 commit comments