adjust vtt cue line to avoid occlusion by video controls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-03-06 11:34:00 +02:00
parent 562bf3be22
commit d8d293cbe6
2 changed files with 35 additions and 1 deletions
@@ -37,7 +37,7 @@ export function VideoEmbedInnerWeb({
throw error
}
const {hlsRef, loop} = useHLS({
const {hlsRef, loop, cueLineRef} = useHLS({
playlist: embed.playlist,
setHasSubtitleTrack,
setError,
@@ -90,6 +90,7 @@ export function VideoEmbedInnerWeb({
hasSubtitleTrack={hasSubtitleTrack}
isGif={embed.presentation === 'gif'}
altText={embed.alt}
cueLineRef={cueLineRef}
/>
</div>
</View>
@@ -145,6 +146,7 @@ function useHLS({
}, [Hls, setHlsLoading])
const hlsRef = useRef<HlsTypes.default | undefined>(undefined)
const cueLineRef = useRef<number>(-2)
const [lowQualityFragments, setLowQualityFragments] = useState<
HlsTypes.Fragment[]
>([])
@@ -220,6 +222,14 @@ function useHLS({
}
})
hls.on(Hls.Events.CUES_PARSED, (_event, data) => {
if (Array.isArray(data.cues)) {
for (const cue of data.cues) {
cue.line = cueLineRef.current
}
}
})
hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => {
if (frag.level === 0) {
setLowQualityFragments(prev => [...prev, frag])
@@ -307,5 +317,6 @@ function useHLS({
return {
hlsRef,
loop: !hasLowQualityFragmentAtStart,
cueLineRef,
}
}
@@ -48,6 +48,7 @@ export function Controls({
hasSubtitleTrack,
isGif,
altText,
cueLineRef,
}: {
videoRef: React.RefObject<HTMLVideoElement | null>
hlsRef: React.RefObject<Hls | undefined | null>
@@ -61,6 +62,7 @@ export function Controls({
hasSubtitleTrack: boolean
isGif: boolean
altText?: string
cueLineRef: React.RefObject<number>
}) {
const {
play,
@@ -294,6 +296,27 @@ export function Controls({
((focused || autoplayDisabled) && !playing) ||
(interactingViaKeypress ? hasFocus : hovered)
// adjust subtitle cue positioning to avoid occlusion by controls
const cueLine = showControls ? -4 : -2
useEffect(() => {
cueLineRef.current = cueLine
const video = videoRef.current
if (!video) return
for (let i = 0; i < video.textTracks.length; i++) {
const track = video.textTracks[i]
if (track.cues) {
for (let j = 0; j < track.cues.length; j++) {
;(track.cues[j] as VTTCue).line = cueLine
}
}
// toggle track mode to force the browser to re-render active cues
if (track.mode === 'showing') {
track.mode = 'hidden'
track.mode = 'showing'
}
}
}, [cueLine, videoRef, cueLineRef])
if (isGif) {
return (
<GifPresentationControls