diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx index de3c130c26..645e398478 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx @@ -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} /> @@ -145,6 +146,7 @@ function useHLS({ }, [Hls, setHlsLoading]) const hlsRef = useRef(undefined) + const cueLineRef = useRef(-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, } } diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx index d6a5628bb4..0b5add305d 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx @@ -48,6 +48,7 @@ export function Controls({ hasSubtitleTrack, isGif, altText, + cueLineRef, }: { videoRef: React.RefObject hlsRef: React.RefObject @@ -61,6 +62,7 @@ export function Controls({ hasSubtitleTrack: boolean isGif: boolean altText?: string + cueLineRef: React.RefObject }) { 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 (