use %, extract out to function
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useEffect, useId, useRef, useState} from 'react'
|
import {useCallback, useEffect, useId, useRef, useState} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type AppBskyEmbedVideo} from '@atproto/api'
|
import {type AppBskyEmbedVideo} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
@@ -37,7 +37,7 @@ export function VideoEmbedInnerWeb({
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
const {hlsRef, loop, cueLineRef} = useHLS({
|
const {hlsRef, loop, updateCuePositions} = useHLS({
|
||||||
playlist: embed.playlist,
|
playlist: embed.playlist,
|
||||||
setHasSubtitleTrack,
|
setHasSubtitleTrack,
|
||||||
setError,
|
setError,
|
||||||
@@ -90,7 +90,7 @@ export function VideoEmbedInnerWeb({
|
|||||||
hasSubtitleTrack={hasSubtitleTrack}
|
hasSubtitleTrack={hasSubtitleTrack}
|
||||||
isGif={embed.presentation === 'gif'}
|
isGif={embed.presentation === 'gif'}
|
||||||
altText={embed.alt}
|
altText={embed.alt}
|
||||||
cueLineRef={cueLineRef}
|
updateCuePositions={updateCuePositions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</View>
|
</View>
|
||||||
@@ -146,7 +146,47 @@ function useHLS({
|
|||||||
}, [Hls, setHlsLoading])
|
}, [Hls, setHlsLoading])
|
||||||
|
|
||||||
const hlsRef = useRef<HlsTypes.default | undefined>(undefined)
|
const hlsRef = useRef<HlsTypes.default | undefined>(undefined)
|
||||||
const cueLineRef = useRef<number>(-2)
|
const controlsVisibleRef = useRef(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repositions VTT subtitle cues using percentage-based line values
|
||||||
|
* (snapToLines=false) so that multi-line/wrapped cues grow upward
|
||||||
|
* instead of extending offscreen. Moves cues higher when controls
|
||||||
|
* are visible to avoid occlusion by the scrub bar.
|
||||||
|
*
|
||||||
|
* Called from two sites:
|
||||||
|
* - SUBTITLE_FRAG_PROCESSED: applies positioning to newly loaded cues
|
||||||
|
* - VideoControls effect: updates positioning when controls show/hide
|
||||||
|
*/
|
||||||
|
const updateCuePositions = useCallback(
|
||||||
|
(controlsVisible?: boolean) => {
|
||||||
|
if (controlsVisible != null) {
|
||||||
|
// save controlsVisible state so that when it's called from SUBTITLE_FRAG_PROCESSED,
|
||||||
|
// the most recent value is used (as we won't know the control state there)
|
||||||
|
controlsVisibleRef.current = controlsVisible
|
||||||
|
}
|
||||||
|
// magic numbers: cue position, % from top of video
|
||||||
|
const line = controlsVisibleRef.current ? 70 : 85
|
||||||
|
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++) {
|
||||||
|
const cue = track.cues[j] as VTTCue
|
||||||
|
cue.snapToLines = false
|
||||||
|
cue.line = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// toggle track mode to force the browser to re-render active cues
|
||||||
|
if (track.mode === 'showing') {
|
||||||
|
track.mode = 'hidden'
|
||||||
|
track.mode = 'showing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[videoRef],
|
||||||
|
)
|
||||||
const [lowQualityFragments, setLowQualityFragments] = useState<
|
const [lowQualityFragments, setLowQualityFragments] = useState<
|
||||||
HlsTypes.Fragment[]
|
HlsTypes.Fragment[]
|
||||||
>([])
|
>([])
|
||||||
@@ -222,12 +262,8 @@ function useHLS({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
hls.on(Hls.Events.CUES_PARSED, (_event, data) => {
|
hls.on(Hls.Events.SUBTITLE_FRAG_PROCESSED, () => {
|
||||||
if (Array.isArray(data.cues)) {
|
updateCuePositions()
|
||||||
for (const cue of data.cues) {
|
|
||||||
cue.line = cueLineRef.current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => {
|
hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => {
|
||||||
@@ -317,6 +353,6 @@ function useHLS({
|
|||||||
return {
|
return {
|
||||||
hlsRef,
|
hlsRef,
|
||||||
loop: !hasLowQualityFragmentAtStart,
|
loop: !hasLowQualityFragmentAtStart,
|
||||||
cueLineRef,
|
updateCuePositions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-20
@@ -48,7 +48,7 @@ export function Controls({
|
|||||||
hasSubtitleTrack,
|
hasSubtitleTrack,
|
||||||
isGif,
|
isGif,
|
||||||
altText,
|
altText,
|
||||||
cueLineRef,
|
updateCuePositions,
|
||||||
}: {
|
}: {
|
||||||
videoRef: React.RefObject<HTMLVideoElement | null>
|
videoRef: React.RefObject<HTMLVideoElement | null>
|
||||||
hlsRef: React.RefObject<Hls | undefined | null>
|
hlsRef: React.RefObject<Hls | undefined | null>
|
||||||
@@ -62,7 +62,7 @@ export function Controls({
|
|||||||
hasSubtitleTrack: boolean
|
hasSubtitleTrack: boolean
|
||||||
isGif: boolean
|
isGif: boolean
|
||||||
altText?: string
|
altText?: string
|
||||||
cueLineRef: React.RefObject<number>
|
updateCuePositions: (controlsVisible?: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
play,
|
play,
|
||||||
@@ -297,25 +297,11 @@ export function Controls({
|
|||||||
(interactingViaKeypress ? hasFocus : hovered)
|
(interactingViaKeypress ? hasFocus : hovered)
|
||||||
|
|
||||||
// adjust subtitle cue positioning to avoid occlusion by controls
|
// adjust subtitle cue positioning to avoid occlusion by controls
|
||||||
const cueLine = showControls ? -4 : -2
|
// uses percentage-based positioning (snapToLines=false) so wrapped
|
||||||
|
// multi-line cues grow upward instead of extending offscreen
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cueLineRef.current = cueLine
|
updateCuePositions(showControls)
|
||||||
const video = videoRef.current
|
}, [showControls, updateCuePositions])
|
||||||
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) {
|
if (isGif) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user