From 096670a0c349f8a47538f2744f6961e4818bc62c Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Tue, 20 Jan 2026 22:15:00 +0100 Subject: [PATCH] fix(tv): better seek --- components/tv/TVFocusableProgressBar.tsx | 25 +++- .../video-player/controls/Controls.tv.tsx | 116 ++++++++++-------- 2 files changed, 86 insertions(+), 55 deletions(-) diff --git a/components/tv/TVFocusableProgressBar.tsx b/components/tv/TVFocusableProgressBar.tsx index a33c072b..42cec6b1 100644 --- a/components/tv/TVFocusableProgressBar.tsx +++ b/components/tv/TVFocusableProgressBar.tsx @@ -78,13 +78,15 @@ export const TVFocusableProgressBar: React.FC = style={[ styles.animatedContainer, animatedStyle, - { - borderColor: focused ? "rgba(255,255,255,0.8)" : "transparent", - borderWidth: 2, - }, + focused && styles.animatedContainerFocused, ]} > - + {cacheProgress && ( = const styles = StyleSheet.create({ pressableContainer: { // Add padding for focus scale animation to not clip - paddingVertical: 4, + paddingVertical: 8, paddingHorizontal: 4, }, animatedContainer: { @@ -112,12 +114,23 @@ const styles = StyleSheet.create({ borderRadius: 12, paddingHorizontal: 4, }, + animatedContainerFocused: { + // Subtle glow effect when focused + shadowColor: "#fff", + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.5, + shadowRadius: 12, + }, progressTrack: { height: PROGRESS_BAR_HEIGHT, backgroundColor: "rgba(255,255,255,0.2)", borderRadius: 8, overflow: "hidden", }, + progressTrackFocused: { + // Brighter track when focused + backgroundColor: "rgba(255,255,255,0.35)", + }, cacheProgress: { position: "absolute", top: 0, diff --git a/components/video-player/controls/Controls.tv.tsx b/components/video-player/controls/Controls.tv.tsx index b5ac3364..ee6565da 100644 --- a/components/video-player/controls/Controls.tv.tsx +++ b/components/video-player/controls/Controls.tv.tsx @@ -200,8 +200,23 @@ export const Controls: FC = ({ controlsOpacity.value = withTiming(showControls ? 1 : 0, animationConfig); bottomTranslateY.value = withTiming(showControls ? 0 : 30, animationConfig); + + // Hide minimal seek bar immediately when normal controls show + if (showControls) { + setShowMinimalSeekBar(false); + if (minimalSeekBarTimeoutRef.current) { + clearTimeout(minimalSeekBarTimeoutRef.current); + minimalSeekBarTimeoutRef.current = null; + } + } }, [showControls, controlsOpacity, bottomTranslateY]); + // Overlay only fades, no slide + const overlayAnimatedStyle = useAnimatedStyle(() => ({ + opacity: controlsOpacity.value, + })); + + // Bottom controls fade and slide up const bottomAnimatedStyle = useAnimatedStyle(() => ({ opacity: controlsOpacity.value, transform: [{ translateY: bottomTranslateY.value }], @@ -689,7 +704,7 @@ export const Controls: FC = ({ return ( @@ -704,13 +719,14 @@ export const Controls: FC = ({ )} {/* Minimal seek bar - shows only progress bar when seeking while controls hidden */} + {/* Uses exact same layout as normal controls for alignment */} = ({ ]} > {showSeekBubble && ( - + = ({ )} - - - ({ - width: `${max.value > 0 ? (cacheProgress.value / max.value) * 100 : 0}%`, - })), - ]} - /> - ({ - width: `${max.value > 0 ? (effectiveProgress.value / max.value) * 100 : 0}%`, - })), - ]} - /> + {/* Same padding as TVFocusableProgressBar for alignment */} + + + + ({ + width: `${max.value > 0 ? (cacheProgress.value / max.value) * 100 : 0}%`, + })), + ]} + /> + ({ + width: `${max.value > 0 ? (effectiveProgress.value / max.value) * 100 : 0}%`, + })), + ]} + /> + - + {formatTimeString(currentTime, "ms")} - - -{formatTimeString(remainingTime, "ms")} - + + + -{formatTimeString(remainingTime, "ms")} + + + {t("player.ends_at")} {getFinishTime()} + + @@ -905,18 +931,10 @@ export const Controls: FC = ({ const styles = StyleSheet.create({ controlsContainer: { - position: "absolute", - top: 0, - left: 0, - right: 0, - bottom: 0, + ...StyleSheet.absoluteFillObject, }, darkOverlay: { - position: "absolute", - top: 0, - left: 0, - right: 0, - bottom: 0, + ...StyleSheet.absoluteFillObject, backgroundColor: "rgba(0, 0, 0, 0.4)", }, bottomContainer: { @@ -1017,21 +1035,21 @@ const styles = StyleSheet.create({ right: 0, zIndex: 5, }, - minimalSeekBarInner: { - flexDirection: "column", + minimalProgressWrapper: { + // Match TVFocusableProgressBar padding for alignment + paddingVertical: 8, + paddingHorizontal: 4, }, - minimalTrickplayContainer: { - alignItems: "center", - marginBottom: 16, + minimalProgressGlow: { + // Same glow effect and scale as focused TVFocusableProgressBar + shadowColor: "#fff", + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.5, + shadowRadius: 12, + transform: [{ scale: 1.02 }], }, - minimalProgressContainer: { - height: TV_SEEKBAR_HEIGHT, - justifyContent: "center", - marginBottom: 8, - }, - minimalTimeContainer: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", + minimalProgressTrack: { + // Brighter track like focused state + backgroundColor: "rgba(255,255,255,0.35)", }, });