Skip to content

Commit 1d89ada

Browse files
committed
MediaPlayerSeek: support fallbackDuration
1 parent f7aa0c0 commit 1d89ada

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

apps/web/app/s/[videoId]/_components/CapVideoPlayer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ export function CapVideoPlayer({
713713
>
714714
<MediaPlayerControlsOverlay className="rounded-b-xl" />
715715
<MediaPlayerSeek
716+
fallbackDuration={playerDuration}
716717
tooltipThumbnailSrc={
717718
isMobile || !resolvedSrc.isSuccess
718719
? undefined

apps/web/app/s/[videoId]/_components/HLSVideoPlayer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ export function HLSVideoPlayer({
571571
isUploadingOrFailed={hasActiveProgress || hasFailedOrError}
572572
>
573573
<MediaPlayerControlsOverlay />
574-
<MediaPlayerSeek />
574+
<MediaPlayerSeek fallbackDuration={playerDuration} />
575575
<div className="flex gap-2 items-center w-full">
576576
<div className="flex flex-1 gap-2 items-center">
577577
<MediaPlayerPlay />

apps/web/app/s/[videoId]/_components/video/media-player.tsx

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)