this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Adjust VTT cue line to avoid occlusion by video controls (#10017)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by

Samuel Newman
Claude Opus 4.6
and committed by
GitHub
5e8ef6aa 177bdcd2

+58 -2
+49 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
··· 1 - import {useEffect, useId, useRef, useState} from 'react' 1 + import {useCallback, useEffect, useId, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {type AppBskyEmbedVideo} from '@atproto/api' 4 4 import {msg} from '@lingui/core/macro' ··· 37 37 throw error 38 38 } 39 39 40 - const {hlsRef, loop} = useHLS({ 40 + const {hlsRef, loop, updateCuePositions} = useHLS({ 41 41 playlist: embed.playlist, 42 42 setHasSubtitleTrack, 43 43 setError, ··· 90 90 hasSubtitleTrack={hasSubtitleTrack} 91 91 isGif={embed.presentation === 'gif'} 92 92 altText={embed.alt} 93 + updateCuePositions={updateCuePositions} 93 94 /> 94 95 </div> 95 96 </View> ··· 145 146 }, [Hls, setHlsLoading]) 146 147 147 148 const hlsRef = useRef<HlsTypes.default | undefined>(undefined) 149 + const controlsVisibleRef = useRef(false) 150 + 151 + /** 152 + * Repositions VTT subtitle cues using percentage-based line values 153 + * (snapToLines=false) so that multi-line/wrapped cues grow upward 154 + * instead of extending offscreen. Moves cues higher when controls 155 + * are visible to avoid occlusion by the scrub bar. 156 + * 157 + * Called from two sites: 158 + * - SUBTITLE_FRAG_PROCESSED: applies positioning to newly loaded cues 159 + * - VideoControls effect: updates positioning when controls show/hide 160 + */ 161 + const updateCuePositions = useCallback( 162 + (controlsVisible?: boolean) => { 163 + if (controlsVisible != null) { 164 + // save controlsVisible state so that when it's called from SUBTITLE_FRAG_PROCESSED, 165 + // the most recent value is used (as we won't know the control state there) 166 + controlsVisibleRef.current = controlsVisible 167 + } 168 + // magic numbers: cue position, % from top of video 169 + const line = controlsVisibleRef.current ? 70 : 85 170 + const video = videoRef.current 171 + if (!video) return 172 + for (let i = 0; i < video.textTracks.length; i++) { 173 + const track = video.textTracks[i] 174 + if (track.cues) { 175 + for (let j = 0; j < track.cues.length; j++) { 176 + const cue = track.cues[j] as VTTCue 177 + cue.snapToLines = false 178 + cue.line = line 179 + } 180 + } 181 + // toggle track mode to force the browser to re-render active cues 182 + if (track.mode === 'showing') { 183 + track.mode = 'hidden' 184 + track.mode = 'showing' 185 + } 186 + } 187 + }, 188 + [videoRef], 189 + ) 148 190 const [lowQualityFragments, setLowQualityFragments] = useState< 149 191 HlsTypes.Fragment[] 150 192 >([]) ··· 218 260 if (data.subtitleTracks.length > 0) { 219 261 setHasSubtitleTrack(true) 220 262 } 263 + }) 264 + 265 + hls.on(Hls.Events.SUBTITLE_FRAG_PROCESSED, () => { 266 + updateCuePositions() 221 267 }) 222 268 223 269 hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => { ··· 307 353 return { 308 354 hlsRef, 309 355 loop: !hasLowQualityFragmentAtStart, 356 + updateCuePositions, 310 357 } 311 358 }
+9
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 48 48 hasSubtitleTrack, 49 49 isGif, 50 50 altText, 51 + updateCuePositions, 51 52 }: { 52 53 videoRef: React.RefObject<HTMLVideoElement | null> 53 54 hlsRef: React.RefObject<Hls | undefined | null> ··· 61 62 hasSubtitleTrack: boolean 62 63 isGif: boolean 63 64 altText?: string 65 + updateCuePositions: (controlsVisible?: boolean) => void 64 66 }) { 65 67 const { 66 68 play, ··· 293 295 const showControls = 294 296 ((focused || autoplayDisabled) && !playing) || 295 297 (interactingViaKeypress ? hasFocus : hovered) 298 + 299 + // adjust subtitle cue positioning to avoid occlusion by controls 300 + // uses percentage-based positioning (snapToLines=false) so wrapped 301 + // multi-line cues grow upward instead of extending offscreen 302 + useEffect(() => { 303 + updateCuePositions(showControls) 304 + }, [showControls, updateCuePositions]) 296 305 297 306 if (isGif) { 298 307 return (