@@ -1469,6 +1469,7 @@ interface MediaPlayerSeekProps
14691469 withTime ?: boolean ;
14701470 withoutChapter ?: boolean ;
14711471 withoutTooltip ?: boolean ;
1472+ fallbackDuration ?: number | null ;
14721473 tooltipThumbnailSrc ?: string | ( ( time : number ) => string ) ;
14731474 tooltipTimeVariant ?: "current" | "progress" ;
14741475 tooltipSideOffset ?: number ;
@@ -1483,6 +1484,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
14831484 withTime = false ,
14841485 withoutChapter = false ,
14851486 withoutTooltip = false ,
1487+ fallbackDuration,
14861488 tooltipTimeVariant = "current" ,
14871489 tooltipThumbnailSrc,
14881490 tooltipSideOffset,
@@ -1499,6 +1501,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
14991501 const mediaCurrentTime = useMediaSelector (
15001502 ( state ) => state . mediaCurrentTime ?? 0 ,
15011503 ) ;
1504+ const mediaDuration = useMediaSelector ( ( state ) => state . mediaDuration ?? 0 ) ;
15021505 const [ seekableStart = 0 , seekableEnd = 0 ] = useMediaSelector (
15031506 ( state ) => state . mediaSeekable ?? [ 0 , 0 ] ,
15041507 ) ;
@@ -1547,6 +1550,25 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
15471550 const lastSeekCommitTimeRef = React . useRef < number > ( 0 ) ;
15481551
15491552 const timeCache = React . useRef < Map < number , string > > ( new Map ( ) ) ;
1553+ const resolvedDuration = React . useMemo ( ( ) => {
1554+ const candidates = [
1555+ mediaDuration ,
1556+ seekableEnd ,
1557+ fallbackDuration ?? 0 ,
1558+ ] . filter ( ( duration ) => Number . isFinite ( duration ) && duration > 0 ) ;
1559+
1560+ return candidates . length > 0 ? Math . max ( ...candidates ) : 0 ;
1561+ } , [ fallbackDuration , mediaDuration , seekableEnd ] ) ;
1562+ const lastKnownDurationRef = React . useRef ( resolvedDuration ) ;
1563+
1564+ React . useEffect ( ( ) => {
1565+ if ( resolvedDuration > 0 ) {
1566+ lastKnownDurationRef . current = resolvedDuration ;
1567+ }
1568+ } , [ resolvedDuration ] ) ;
1569+
1570+ const effectiveDuration =
1571+ resolvedDuration > 0 ? resolvedDuration : lastKnownDurationRef . current ;
15501572
15511573 const displayValue = seekState . pendingSeekTime ?? mediaCurrentTime ;
15521574
@@ -1575,9 +1597,12 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
15751597 return formatted ;
15761598 } , [ ] ) ;
15771599
1578- const currentTime = getCachedTime ( displayValue , seekableEnd ) ;
1579- const duration = getCachedTime ( seekableEnd , seekableEnd ) ;
1580- const remainingTime = getCachedTime ( seekableEnd - displayValue , seekableEnd ) ;
1600+ const currentTime = getCachedTime ( displayValue , effectiveDuration ) ;
1601+ const duration = getCachedTime ( effectiveDuration , effectiveDuration ) ;
1602+ const remainingTime = getCachedTime (
1603+ effectiveDuration - displayValue ,
1604+ effectiveDuration ,
1605+ ) ;
15811606
15821607 const onCollisionDataUpdate = React . useCallback ( ( ) => {
15831608 if ( collisionDataRef . current ) return collisionDataRef . current ;
@@ -1720,17 +1745,17 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
17201745 ) ;
17211746
17221747 const onHoverProgressUpdate = React . useCallback ( ( ) => {
1723- if ( ! seekRef . current || seekableEnd <= 0 ) return ;
1748+ if ( ! seekRef . current || effectiveDuration <= 0 ) return ;
17241749
17251750 const hoverPercent = Math . min (
17261751 100 ,
1727- ( hoverTimeRef . current / seekableEnd ) * 100 ,
1752+ ( hoverTimeRef . current / effectiveDuration ) * 100 ,
17281753 ) ;
17291754 seekRef . current . style . setProperty (
17301755 SEEK_HOVER_PERCENT ,
17311756 `${ hoverPercent . toFixed ( 4 ) } %` ,
17321757 ) ;
1733- } , [ seekableEnd ] ) ;
1758+ } , [ effectiveDuration ] ) ;
17341759
17351760 React . useEffect ( ( ) => {
17361761 if ( seekState . pendingSeekTime !== null ) {
@@ -1763,7 +1788,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
17631788 } , [ dispatch , seekState . isHovering , tooltipDisabled ] ) ;
17641789
17651790 const bufferedProgress = React . useMemo ( ( ) => {
1766- if ( mediaBuffered . length === 0 || seekableEnd <= 0 ) return 0 ;
1791+ if ( mediaBuffered . length === 0 || effectiveDuration <= 0 ) return 0 ;
17671792
17681793 if ( mediaEnded ) return 1 ;
17691794
@@ -1772,11 +1797,17 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
17721797 ) ;
17731798
17741799 if ( containingRange ) {
1775- return Math . min ( 1 , containingRange [ 1 ] / seekableEnd ) ;
1800+ return Math . min ( 1 , containingRange [ 1 ] / effectiveDuration ) ;
17761801 }
17771802
1778- return Math . min ( 1 , seekableStart / seekableEnd ) ;
1779- } , [ mediaBuffered , mediaCurrentTime , seekableEnd , mediaEnded , seekableStart ] ) ;
1803+ return Math . min ( 1 , seekableStart / effectiveDuration ) ;
1804+ } , [
1805+ effectiveDuration ,
1806+ mediaBuffered ,
1807+ mediaCurrentTime ,
1808+ mediaEnded ,
1809+ seekableStart ,
1810+ ] ) ;
17801811
17811812 const onPointerEnter = React . useCallback ( ( ) => {
17821813 if ( seekRef . current ) {
@@ -1788,7 +1819,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
17881819 horizontalMovementRef . current = 0 ;
17891820 verticalMovementRef . current = 0 ;
17901821
1791- if ( seekableEnd > 0 ) {
1822+ if ( effectiveDuration > 0 ) {
17921823 if ( hoverTimeoutRef . current ) {
17931824 clearTimeout ( hoverTimeoutRef . current ) ;
17941825 }
@@ -1803,7 +1834,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
18031834 }
18041835 }
18051836 }
1806- } , [ seekableEnd , onTooltipPositionUpdate , tooltipDisabled ] ) ;
1837+ } , [ effectiveDuration , onTooltipPositionUpdate , tooltipDisabled ] ) ;
18071838
18081839 const onPointerLeave = React . useCallback ( ( ) => {
18091840 if ( hoverTimeoutRef . current ) {
@@ -1846,7 +1877,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
18461877
18471878 const onPointerMove = React . useCallback (
18481879 ( event : React . PointerEvent < HTMLDivElement > ) => {
1849- if ( seekableEnd <= 0 ) return ;
1880+ if ( effectiveDuration <= 0 ) return ;
18501881
18511882 if ( ! seekRectRef . current && seekRef . current ) {
18521883 seekRectRef . current = seekRef . current . getBoundingClientRect ( ) ;
@@ -1890,7 +1921,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
18901921 Math . min ( clientX - seekRect . left , seekRect . width ) ,
18911922 ) ;
18921923 const relativeX = offsetXOnSeekBar / seekRect . width ;
1893- const calculatedHoverTime = relativeX * seekableEnd ;
1924+ const calculatedHoverTime = relativeX * effectiveDuration ;
18941925
18951926 hoverTimeRef . current = calculatedHoverTime ;
18961927
@@ -1940,7 +1971,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
19401971 onPreviewUpdate ,
19411972 onTooltipPositionUpdate ,
19421973 onHoverProgressUpdate ,
1943- seekableEnd ,
1974+ effectiveDuration ,
19441975 seekState . isHovering ,
19451976 tooltipDisabled ,
19461977 ] ,
@@ -2045,15 +2076,15 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
20452076
20462077 const currentChapterCue = getCurrentChapterCue ( hoverTimeRef . current ) ;
20472078 const thumbnail = getThumbnail ( hoverTimeRef . current ) ;
2048- const hoverTime = getCachedTime ( hoverTimeRef . current , seekableEnd ) ;
2079+ const hoverTime = getCachedTime ( hoverTimeRef . current , effectiveDuration ) ;
20492080
20502081 const chapterSeparators = React . useMemo ( ( ) => {
2051- if ( withoutChapter || chapterCues . length <= 1 || seekableEnd <= 0 ) {
2082+ if ( withoutChapter || chapterCues . length <= 1 || effectiveDuration <= 0 ) {
20522083 return null ;
20532084 }
20542085
20552086 return chapterCues . slice ( 1 ) . map ( ( chapterCue , index ) => {
2056- const position = ( chapterCue . startTime / seekableEnd ) * 100 ;
2087+ const position = ( chapterCue . startTime / effectiveDuration ) * 100 ;
20572088
20582089 return (
20592090 < div
@@ -2070,7 +2101,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
20702101 />
20712102 ) ;
20722103 } ) ;
2073- } , [ chapterCues , seekableEnd , withoutChapter ] ) ;
2104+ } , [ chapterCues , effectiveDuration , withoutChapter ] ) ;
20742105
20752106 const spriteStyle = React . useMemo < React . CSSProperties > ( ( ) => {
20762107 if ( ! thumbnail ?. coords || ! thumbnail ?. src ) {
@@ -2111,7 +2142,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
21112142 { ...seekProps }
21122143 ref = { seekRef }
21132144 min = { seekableStart }
2114- max = { seekableEnd }
2145+ max = { effectiveDuration }
21152146 step = { 0.01 }
21162147 className = { cn (
21172148 "flex relative items-center w-full select-none touch-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50" ,
@@ -2133,7 +2164,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
21332164 } }
21342165 />
21352166 < SliderPrimitive . Range className = "absolute h-full bg-white will-change-[width]" />
2136- { seekState . isHovering && seekableEnd > 0 && (
2167+ { seekState . isHovering && effectiveDuration > 0 && (
21372168 < div
21382169 data-slot = "media-player-seek-hover-range"
21392170 className = "absolute h-full bg-white/70 will-change-[width,opacity]"
@@ -2150,7 +2181,7 @@ function MediaPlayerSeek(props: MediaPlayerSeekProps) {
21502181 { ! withoutTooltip &&
21512182 ! context . withoutTooltip &&
21522183 seekState . isHovering &&
2153- seekableEnd > 0 && (
2184+ effectiveDuration > 0 && (
21542185 < MediaPlayerPortal >
21552186 < div
21562187 ref = { tooltipRef }
0 commit comments