smarter video source handling
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useMemo, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
LayoutAnimation,
|
LayoutAnimation,
|
||||||
ListRenderItem,
|
ListRenderItem,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
|
||||||
import {runOnJS} from 'react-native-reanimated'
|
import {runOnJS} from 'react-native-reanimated'
|
||||||
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {useEvent} from 'expo'
|
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
import {useVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
|
import {useVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
|
||||||
import {
|
import {
|
||||||
@@ -101,6 +100,10 @@ function YoloFeed() {
|
|||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = usePostFeedQuery(`feedgen|${VIBES_FEED_URI}`)
|
} = usePostFeedQuery(`feedgen|${VIBES_FEED_URI}`)
|
||||||
|
|
||||||
|
const [currentSources, setCurrentSources] = useState<
|
||||||
|
[string | null, string | null, string | null]
|
||||||
|
>([null, null, null])
|
||||||
|
|
||||||
const player1 = useVideoPlayer('', p => {
|
const player1 = useVideoPlayer('', p => {
|
||||||
p.loop = true
|
p.loop = true
|
||||||
})
|
})
|
||||||
@@ -125,27 +128,89 @@ function YoloFeed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const player = [player1, player2, player3][index % 3]
|
const player = [player1, player2, player3][index % 3]
|
||||||
|
const currentSource = currentSources[index % 3]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VibeItem
|
<VibeItem
|
||||||
player={player}
|
player={player}
|
||||||
post={post}
|
post={post}
|
||||||
embed={post.embed}
|
embed={post.embed}
|
||||||
loaded={isFocused && Math.abs(index - currentIndex) < 2}
|
loaded={
|
||||||
|
isFocused &&
|
||||||
|
Math.abs(index - currentIndex) < 2 &&
|
||||||
|
currentSource === post.embed.playlist
|
||||||
|
}
|
||||||
active={isFocused && index === currentIndex}
|
active={isFocused && index === currentIndex}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[player1, player2, player3, currentIndex, isFocused],
|
[player1, player2, player3, currentIndex, isFocused, currentSources],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const updateVideoState = useNonReactiveCallback((index: number) => {
|
||||||
|
if (!videos) return
|
||||||
|
setCurrentIndex(index)
|
||||||
|
setCurrentSources(oldSources => {
|
||||||
|
const currentSources = [...oldSources] as [
|
||||||
|
string | null,
|
||||||
|
string | null,
|
||||||
|
string | null,
|
||||||
|
]
|
||||||
|
|
||||||
|
const prevEmbed = videos[index - 1]?.post.embed
|
||||||
|
const prevVideo =
|
||||||
|
prevEmbed && AppBskyEmbedVideo.isView(prevEmbed)
|
||||||
|
? prevEmbed.playlist
|
||||||
|
: null
|
||||||
|
const currEmbed = videos[index]?.post.embed
|
||||||
|
const currVideo =
|
||||||
|
currEmbed && AppBskyEmbedVideo.isView(currEmbed)
|
||||||
|
? currEmbed.playlist
|
||||||
|
: null
|
||||||
|
const nextEmbed = videos[index + 1]?.post.embed
|
||||||
|
const nextVideo =
|
||||||
|
nextEmbed && AppBskyEmbedVideo.isView(nextEmbed)
|
||||||
|
? nextEmbed.playlist
|
||||||
|
: null
|
||||||
|
|
||||||
|
const prevPlayer = [player1, player2, player3][(index + 2) % 3]
|
||||||
|
const prevPlayerCurrentSource = currentSources[(index + 2) % 3]
|
||||||
|
const currPlayer = [player1, player2, player3][index % 3]
|
||||||
|
const currPlayerCurrentSource = currentSources[index % 3]
|
||||||
|
const nextPlayer = [player1, player2, player3][(index + 1) % 3]
|
||||||
|
const nextPlayerCurrentSource = currentSources[(index + 1) % 3]
|
||||||
|
|
||||||
|
if (prevVideo && prevVideo !== prevPlayerCurrentSource) {
|
||||||
|
prevPlayer.replace(prevVideo)
|
||||||
|
currentSources[index + (2 % 3)] = prevVideo
|
||||||
|
}
|
||||||
|
prevPlayer.pause()
|
||||||
|
|
||||||
|
if (currVideo) {
|
||||||
|
if (currVideo !== currPlayerCurrentSource) {
|
||||||
|
currPlayer.replace(currVideo)
|
||||||
|
currentSources[index % 3] = currVideo
|
||||||
|
}
|
||||||
|
currPlayer.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextVideo && nextVideo !== nextPlayerCurrentSource) {
|
||||||
|
nextPlayer.replace(nextVideo)
|
||||||
|
currentSources[(index + 1) % 3] = nextVideo
|
||||||
|
}
|
||||||
|
nextPlayer.pause()
|
||||||
|
|
||||||
|
return currentSources
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const onViewableItemsChanged = useCallback(
|
const onViewableItemsChanged = useCallback(
|
||||||
({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => {
|
({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => {
|
||||||
if (viewableItems[0] && viewableItems[0].index !== null) {
|
if (viewableItems[0] && viewableItems[0].index !== null) {
|
||||||
setCurrentIndex(viewableItems[0].index)
|
updateVideoState(viewableItems[0].index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[updateVideoState],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -170,7 +235,7 @@ function YoloFeed() {
|
|||||||
}}
|
}}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
onViewableItemsChanged={onViewableItemsChanged}
|
onViewableItemsChanged={onViewableItemsChanged}
|
||||||
viewabilityConfig={{itemVisiblePercentThreshold: 75}}
|
viewabilityConfig={{itemVisiblePercentThreshold: 95}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -183,7 +248,6 @@ function VibeItem({
|
|||||||
player,
|
player,
|
||||||
post,
|
post,
|
||||||
embed,
|
embed,
|
||||||
active,
|
|
||||||
loaded,
|
loaded,
|
||||||
}: {
|
}: {
|
||||||
player: VideoPlayer
|
player: VideoPlayer
|
||||||
@@ -194,42 +258,6 @@ function VibeItem({
|
|||||||
}) {
|
}) {
|
||||||
const {height, width} = useWindowDimensions()
|
const {height, width} = useWindowDimensions()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const source = embed.playlist
|
|
||||||
const sourceChangeEvent = useEvent(player, 'sourceChange') as {
|
|
||||||
// incorrect types
|
|
||||||
source: {uri?: string}
|
|
||||||
oldSource: {uri?: string}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for initial video - useEffect will handle the typical case where
|
|
||||||
// videos have a chance to preload
|
|
||||||
const maybePlay = useNonReactiveCallback(() => {
|
|
||||||
if (active && !player.playing) {
|
|
||||||
player.play()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loaded && sourceChangeEvent?.source?.uri !== source) {
|
|
||||||
player.replace(source)
|
|
||||||
// play next tick
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
maybePlay()
|
|
||||||
}, 0)
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [sourceChangeEvent?.source?.uri, loaded, source, player, maybePlay])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (active) {
|
|
||||||
player.play()
|
|
||||||
} else {
|
|
||||||
// should be a cleanup function, but that causes a crash
|
|
||||||
player.pause()
|
|
||||||
}
|
|
||||||
}, [active, player])
|
|
||||||
|
|
||||||
const screenAspectRatio =
|
const screenAspectRatio =
|
||||||
(width - insets.left - insets.right) / (height - insets.bottom)
|
(width - insets.left - insets.right) / (height - insets.bottom)
|
||||||
@@ -387,10 +415,12 @@ function ExpandableRichTextView({
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
scrollEnabled={expanded}
|
scrollEnabled={expanded}
|
||||||
onContentSizeChange={(_w, h) => {
|
onContentSizeChange={(_w, h) => {
|
||||||
LayoutAnimation.configureNext({
|
if (contentHeight !== 0) {
|
||||||
duration: 500,
|
LayoutAnimation.configureNext({
|
||||||
update: {type: 'spring', springDamping: 0.6},
|
duration: 500,
|
||||||
})
|
update: {type: 'spring', springDamping: 0.6},
|
||||||
|
})
|
||||||
|
}
|
||||||
setContentHeight(h)
|
setContentHeight(h)
|
||||||
}}
|
}}
|
||||||
style={{height: Math.min(contentHeight, screenHeight * 0.5)}}
|
style={{height: Math.min(contentHeight, screenHeight * 0.5)}}
|
||||||
|
|||||||
Reference in New Issue
Block a user