fix(tv): better seek

This commit is contained in:
Fredrik Burmester
2026-01-20 22:15:00 +01:00
parent aa6b441dd1
commit 096670a0c3
2 changed files with 86 additions and 55 deletions

View File

@@ -78,13 +78,15 @@ export const TVFocusableProgressBar: React.FC<TVFocusableProgressBarProps> =
style={[
styles.animatedContainer,
animatedStyle,
{
borderColor: focused ? "rgba(255,255,255,0.8)" : "transparent",
borderWidth: 2,
},
focused && styles.animatedContainerFocused,
]}
>
<View style={styles.progressTrack}>
<View
style={[
styles.progressTrack,
focused && styles.progressTrackFocused,
]}
>
{cacheProgress && (
<ReanimatedView
style={[styles.cacheProgress, cacheProgressStyle]}
@@ -103,7 +105,7 @@ export const TVFocusableProgressBar: React.FC<TVFocusableProgressBarProps> =
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,

View File

@@ -200,8 +200,23 @@ export const Controls: FC<Props> = ({
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<Props> = ({
return (
<View style={styles.controlsContainer} pointerEvents='box-none'>
<Animated.View
style={[styles.darkOverlay, bottomAnimatedStyle]}
style={[styles.darkOverlay, overlayAnimatedStyle]}
pointerEvents='none'
/>
@@ -704,13 +719,14 @@ export const Controls: FC<Props> = ({
)}
{/* Minimal seek bar - shows only progress bar when seeking while controls hidden */}
{/* Uses exact same layout as normal controls for alignment */}
<Animated.View
style={[styles.minimalSeekBarContainer, minimalSeekBarAnimatedStyle]}
pointerEvents={showMinimalSeekBar && !showControls ? "auto" : "none"}
>
<View
style={[
styles.minimalSeekBarInner,
styles.bottomInner,
{
paddingRight: Math.max(insets.right, 48),
paddingLeft: Math.max(insets.left, 48),
@@ -719,7 +735,7 @@ export const Controls: FC<Props> = ({
]}
>
{showSeekBubble && (
<View style={styles.minimalTrickplayContainer}>
<View style={styles.trickplayBubbleContainer}>
<TrickplayBubble
trickPlayUrl={trickPlayUrl}
trickplayInfo={trickplayInfo}
@@ -728,34 +744,44 @@ export const Controls: FC<Props> = ({
</View>
)}
<View style={styles.minimalProgressContainer}>
<View style={styles.progressTrack}>
<Animated.View
style={[
styles.cacheProgress,
useAnimatedStyle(() => ({
width: `${max.value > 0 ? (cacheProgress.value / max.value) * 100 : 0}%`,
})),
]}
/>
<Animated.View
style={[
styles.progressFill,
useAnimatedStyle(() => ({
width: `${max.value > 0 ? (effectiveProgress.value / max.value) * 100 : 0}%`,
})),
]}
/>
{/* Same padding as TVFocusableProgressBar for alignment */}
<View style={styles.minimalProgressWrapper}>
<View
style={[styles.progressBarContainer, styles.minimalProgressGlow]}
>
<View style={[styles.progressTrack, styles.minimalProgressTrack]}>
<Animated.View
style={[
styles.cacheProgress,
useAnimatedStyle(() => ({
width: `${max.value > 0 ? (cacheProgress.value / max.value) * 100 : 0}%`,
})),
]}
/>
<Animated.View
style={[
styles.progressFill,
useAnimatedStyle(() => ({
width: `${max.value > 0 ? (effectiveProgress.value / max.value) * 100 : 0}%`,
})),
]}
/>
</View>
</View>
</View>
<View style={styles.minimalTimeContainer}>
<View style={styles.timeContainer}>
<Text style={styles.timeText}>
{formatTimeString(currentTime, "ms")}
</Text>
<Text style={styles.timeText}>
-{formatTimeString(remainingTime, "ms")}
</Text>
<View style={styles.timeRight}>
<Text style={styles.timeText}>
-{formatTimeString(remainingTime, "ms")}
</Text>
<Text style={styles.endsAtText}>
{t("player.ends_at")} {getFinishTime()}
</Text>
</View>
</View>
</View>
</Animated.View>
@@ -905,18 +931,10 @@ export const Controls: FC<Props> = ({
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)",
},
});