diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 2362c8f43..7a2e17817 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -768,7 +768,7 @@ export default function page() { currentPlayStateInfo() as PlaybackProgressInfo, ); } - if (!Platform.isTV) await activateKeepAwakeAsync(); + await activateKeepAwakeAsync(); return; } @@ -781,7 +781,7 @@ export default function page() { currentPlayStateInfo() as PlaybackProgressInfo, ); } - if (!Platform.isTV) await deactivateKeepAwake(); + await deactivateKeepAwake(); return; } diff --git a/components/video-player/controls/Controls.tv.tsx b/components/video-player/controls/Controls.tv.tsx index f980a0eb7..ba545c88d 100644 --- a/components/video-player/controls/Controls.tv.tsx +++ b/components/video-player/controls/Controls.tv.tsx @@ -14,6 +14,7 @@ import { } from "react"; import { useTranslation } from "react-i18next"; import { + Pressable, StyleSheet, TVFocusGuideView, useWindowDimensions, @@ -278,6 +279,9 @@ export const Controls: FC = ({ null, ); + // Ref for the invisible focus-stealing overlay (prevents hidden buttons from receiving select events) + const focusOverlayRef = useRef(null); + const audioTracks = useMemo(() => { return mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") ?? []; }, [mediaSource]); @@ -908,6 +912,19 @@ export const Controls: FC = ({ setFocusPlayButton(false); }, [setShowControls]); + // When controls hide (and no skip/countdown overlay is visible), move focus + // to the invisible overlay so hidden buttons can't receive select events. + useEffect(() => { + if (!showControls && !isSkipOrCountdownVisible) { + // Small delay to let the controls fade-out animation start and + // the focus engine settle before stealing focus + const t = setTimeout(() => { + focusOverlayRef.current?.focus(); + }, 100); + return () => clearTimeout(t); + } + }, [showControls, isSkipOrCountdownVisible]); + const handleBack = useCallback(() => { router.back(); }, [router]); @@ -1025,6 +1042,24 @@ export const Controls: FC = ({ pointerEvents='none' /> + {/* Invisible overlay that steals focus when controls are hidden. + Prevents hidden control buttons from receiving select/enter events + from the TV remote. Pressing center button here toggles play/pause. */} + { + togglePlay(); + setShowControls(true); + setFocusPlayButton(true); + }} + /> + {getTechnicalInfo && ( = ({ = { - [TVTypographyScale.Small]: 0.9, - [TVTypographyScale.Default]: 1.0, - [TVTypographyScale.Large]: 1.1, - [TVTypographyScale.ExtraLarge]: 1.2, + [TVTypographyScale.Small]: 0.63, + [TVTypographyScale.Default]: 0.7, + [TVTypographyScale.Large]: 0.77, + [TVTypographyScale.ExtraLarge]: 0.84, }; // ============================================================================= diff --git a/constants/TVTypography.ts b/constants/TVTypography.ts index 9c8d2b144..cbac9b693 100644 --- a/constants/TVTypography.ts +++ b/constants/TVTypography.ts @@ -37,10 +37,10 @@ export type TVTypographyKey = keyof typeof TVTypography; // ============================================================================= const scaleMultipliers: Record = { - [TVTypographyScale.Small]: 0.85, - [TVTypographyScale.Default]: 1.0, - [TVTypographyScale.Large]: 1.2, - [TVTypographyScale.ExtraLarge]: 1.4, + [TVTypographyScale.Small]: 0.6, + [TVTypographyScale.Default]: 0.7, + [TVTypographyScale.Large]: 0.84, + [TVTypographyScale.ExtraLarge]: 0.98, }; // =============================================================================