From 0ab4d8faef947544360013bbdee80bb114bb13dc Mon Sep 17 00:00:00 2001 From: Chad Curtis Date: Tue, 3 Mar 2026 01:11:17 -0600 Subject: [PATCH] fix(vines): hold thumbnail until video ready, add buffering spinner, hide UI until canplay --- src/pages/VinesFeedPage.tsx | 261 ++++++++++++++++++++++-------------- 1 file changed, 160 insertions(+), 101 deletions(-) diff --git a/src/pages/VinesFeedPage.tsx b/src/pages/VinesFeedPage.tsx index 5f755f37..d3383813 100644 --- a/src/pages/VinesFeedPage.tsx +++ b/src/pages/VinesFeedPage.tsx @@ -286,6 +286,10 @@ export function VineCard({ event, isActive, isNearActive, onCommentClick }: Vine const [hasStarted, setHasStarted] = useState(false); const [isAttemptingPlay, setIsAttemptingPlay] = useState(isActive); const [isMuted, setIsMuted] = useState(globalMuted); + // true once the browser has decoded enough to render the first frame + const [isVideoReady, setIsVideoReady] = useState(false); + // true when the video is stalling / rebuffering mid-playback + const [isBuffering, setIsBuffering] = useState(false); const videoRef = useRef(null); @@ -295,6 +299,14 @@ export function VineCard({ event, isActive, isNearActive, onCommentClick }: Vine const { src, onError: onBlossomError } = useBlossomFallback(imeta.url ?? ''); + // Reset ready/buffering state when the active vine changes (new src loaded) + useEffect(() => { + setIsVideoReady(false); + setIsBuffering(false); + setHasStarted(false); + setIsPlaying(false); + }, [src]); + // Auto-play / auto-pause based on active state useEffect(() => { const video = videoRef.current; @@ -312,6 +324,7 @@ export function VineCard({ event, isActive, isNearActive, onCommentClick }: Vine video.pause(); video.currentTime = 0; setIsAttemptingPlay(false); + setIsBuffering(false); } }, [isActive, imeta.url]); @@ -344,20 +357,38 @@ export function VineCard({ event, isActive, isNearActive, onCommentClick }: Vine