From 92664f57d567548bdd6985f9c3abc1a9bbb52dc1 Mon Sep 17 00:00:00 2001 From: Samuel Newman <10959775+mozzius@users.noreply.github.com> Date: Tue, 30 Jul 2024 13:06:25 +0200 Subject: [PATCH] start working on controls --- ...rowsDiagonalIn_stroke2_corner2_rounded.svg | 1 + ...owsDiagonalOut_stroke2_corner2_rounded.svg | 1 + assets/icons/pause_filled_corner2_rounded.svg | 1 + .../icons/pause_stroke2_corner2_rounded.svg | 1 + src/components/icons/ArrowsDiagonal.tsx | 9 + src/components/icons/Pause.tsx | 9 + .../com/util/post-embeds/VideoEmbed.web.tsx | 6 +- .../util/post-embeds/VideoEmbedInner.web.tsx | 283 +++++++++++++++--- 8 files changed, 265 insertions(+), 46 deletions(-) create mode 100644 assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg create mode 100644 assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg create mode 100644 assets/icons/pause_filled_corner2_rounded.svg create mode 100644 assets/icons/pause_stroke2_corner2_rounded.svg create mode 100644 src/components/icons/ArrowsDiagonal.tsx create mode 100644 src/components/icons/Pause.tsx diff --git a/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg b/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..9b92e533eb --- /dev/null +++ b/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg b/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..36d8e1d67c --- /dev/null +++ b/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/pause_filled_corner2_rounded.svg b/assets/icons/pause_filled_corner2_rounded.svg new file mode 100644 index 0000000000..98726d873e --- /dev/null +++ b/assets/icons/pause_filled_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/pause_stroke2_corner2_rounded.svg b/assets/icons/pause_stroke2_corner2_rounded.svg new file mode 100644 index 0000000000..3a8c0b4379 --- /dev/null +++ b/assets/icons/pause_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons/ArrowsDiagonal.tsx b/src/components/icons/ArrowsDiagonal.tsx new file mode 100644 index 0000000000..2a955e91f2 --- /dev/null +++ b/src/components/icons/ArrowsDiagonal.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowsDiagonalOut_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M13 4a1 1 0 0 1 1-1h5a2 2 0 0 1 2 2v5a1 1 0 1 1-2 0V6.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L17.586 5H14a1 1 0 0 1-1-1Zm-9 9a1 1 0 0 1 1 1v3.586l4.293-4.293a1 1 0 0 1 1.414 1.414L6.414 19H10a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2v-5a1 1 0 0 1 1-1Z', +}) + +export const ArrowsDiagonalIn_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M20.957 3.043a1 1 0 0 1 0 1.414L16.414 9H20a1 1 0 1 1 0 2h-5a2 2 0 0 1-2-2V4a1 1 0 1 1 2 0v3.586l4.543-4.543a1 1 0 0 1 1.414 0ZM3 14a1 1 0 0 1 1-1h5a2 2 0 0 1 2 2v5a1 1 0 1 1-2 0v-3.586l-4.543 4.543a1 1 0 0 1-1.414-1.414L7.586 15H4a1 1 0 0 1-1-1Z', +}) diff --git a/src/components/icons/Pause.tsx b/src/components/icons/Pause.tsx new file mode 100644 index 0000000000..f83949f06c --- /dev/null +++ b/src/components/icons/Pause.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Pause_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M4 6a3 3 0 0 1 6 0v12a3 3 0 1 1-6 0V6Zm3-1a1 1 0 0 0-1 1v12a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1Zm7 1a3 3 0 1 1 6 0v12a3 3 0 1 1-6 0V6Zm3-1a1 1 0 0 0-1 1v12a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1Z', +}) + +export const Pause_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'M4 6a3 3 0 0 1 6 0v12a3 3 0 1 1-6 0V6ZM14 6a3 3 0 1 1 6 0v12a3 3 0 1 1-6 0V6Z', +}) diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index 1831e36aaf..cccb413613 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -17,6 +17,7 @@ export function VideoEmbed({source}: {source: string}) { source, }) const [onScreen, setOnScreen] = useState(false) + const [hasBeenOnScreen, setHasBeenOnScreen] = useState(false) const {_} = useLingui() const onPress = useCallback(() => setActive(), [setActive]) @@ -28,6 +29,9 @@ export function VideoEmbed({source}: {source: string}) { const entry = entries[0] if (!entry) return setOnScreen(entry.isIntersecting) + if (entry.isIntersecting) { + setHasBeenOnScreen(true) + } sendPosition( entry.boundingClientRect.y + entry.boundingClientRect.height / 2, ) @@ -48,7 +52,7 @@ export function VideoEmbed({source}: {source: string}) { a.my_xs, ]}>
- {onScreen || active ? ( + {hasBeenOnScreen || active ? ( (null) const [focused, setFocused] = useState(false) - const {play, pause, togglePlayPause} = useVideoUtils(ref) - useEffect(() => { if (ref.current) { if (ref.current.canPlayType('application/vnd.apple.mpegurl')) { @@ -107,22 +115,6 @@ export function VideoPlayer({ } }, [source, hls]) - useEffect(() => { - if (active) { - play() - } - return () => { - pause() - setFocused(false) - } - }, [active, play, pause]) - - useEffect(() => { - if (!onScreen) { - pause() - } - }, [onScreen, pause]) - return ( + ) } +function Controls({ + videoRef, + active, + setActive, + focused, + setFocused, + onScreen, +}: { + videoRef: React.RefObject + active: boolean + setActive: () => void + focused: boolean + setFocused: (focused: boolean) => void + onScreen: boolean +}) { + const {play, pause, playing, muted, togglePlayPause, currentTime, duration} = + useVideoUtils(videoRef) + const t = useTheme() + const {_} = useLingui() + const { + state: hovered, + onIn: onMouseEnter, + onOut: onMouseLeave, + } = useInteractionState() + + useEffect(() => { + if (active) { + play() + } + return () => { + pause() + setFocused(false) + } + }, [active, play, pause, setFocused]) + + useEffect(() => { + if (!onScreen) { + pause() + } + }, [onScreen, pause]) + + return ( +
{ + evt.stopPropagation() + if (!focused) { + if (!active) { + setActive() + } + setFocused(true) + } + }} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave}> + {hovered && ( + + + + + {/* TODO: find workaround for iOS Safari */} + + + )} + {(hovered || !focused) && ( + + {duration > 0 && ( + + )} + + )} +
+ ) +} + function useVideoUtils(ref: React.RefObject) { - const play = () => { + const [playing, setPlaying] = useState(false) + const [muted, setMuted] = useState(true) + const [currentTime, setCurrentTime] = useState(0) + const [duration, setDuration] = useState(0) + const [canPlay, setCanPlay] = useState(false) + + useEffect(() => { + if (!ref.current) return + let current = ref.current + + const handleTimeUpdate = () => { + if (!current) return + setCurrentTime(current.currentTime || 0) + } + + const handleDurationChange = () => { + if (!current) return + setDuration(current.duration || 0) + } + + const handlePlay = () => { + setPlaying(true) + } + + const handlePause = () => { + setPlaying(false) + } + + const handleMute = () => { + setMuted(true) + } + + const handleUnmute = () => { + setMuted(false) + } + + const handleCanPlay = () => { + setCanPlay(true) + } + + ref.current.addEventListener('timeupdate', handleTimeUpdate) + ref.current.addEventListener('durationchange', handleDurationChange) + ref.current.addEventListener('play', handlePlay) + ref.current.addEventListener('pause', handlePause) + ref.current.addEventListener('mute', handleMute) + ref.current.addEventListener('unmute', handleUnmute) + ref.current.addEventListener('ended', handlePause) + ref.current.addEventListener('error', handlePause) + ref.current.addEventListener('canplay', handleCanPlay) + + return () => { + current.removeEventListener('timeupdate', handleTimeUpdate) + current.removeEventListener('durationchange', handleDurationChange) + current.removeEventListener('play', handlePlay) + current.removeEventListener('pause', handlePause) + current.removeEventListener('mute', handleMute) + current.removeEventListener('unmute', handleUnmute) + current.removeEventListener('ended', handlePause) + current.removeEventListener('error', handlePause) + current.removeEventListener('canplay', handleCanPlay) + } + }, [ref, playing]) + + // to explain why I'm doing this in an effect - if you call play() + // before it's loaded, at least on firefox, it'll throw when you then + // later go to pause/unload the video. Therefore we should keep + // play state outside of the video, and then only call it when + // playing === true and canPlay === true + useEffect(() => { if (!ref.current) return - // this is how you check if the video is ready to play - if (ref.current.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) { - ref.current.play() + if (playing) { + if (canPlay) { + ref.current.play() + } } else { - // TODO: find different solution. can sometimes cause double-playing - ref.current.addEventListener('canplay', () => { - ref.current?.play() - }) + ref.current.pause() } + }, [playing, ref, canPlay]) + + const play = () => { + setPlaying(true) } const pause = () => { - if (!ref.current) return - ref.current.pause() + setPlaying(false) } const togglePlayPause = () => { - if (!ref.current) return - if (ref.current.paused) { + if (playing) { play() } else { pause() } } - return {play, pause, togglePlayPause} + return {play, pause, togglePlayPause, duration, currentTime, playing, muted} }