Overview of problem
I'm using react-native-video
and on Android, when switching between an <Image />
component and a <Video />
component, the UI that I have absolutely positioned on top flashes/flickers. I'm using the video in a carousel from react-native-reanimated-carousel
and am changing from an image to a video when the slide becomes the current slide. I'm doing this because there is a bug where Exoplayer (the Android implementation used by react-native-video
) can't instantiate more than one or two videos at a time and also to conserve memory.
I've tested without react-native-reanimated-carousel
and have included a basic example below where you can toggle between a video and image component with a button. When toggling between a image component and any other react native component, the UI doesn't flicker.
This loom video shows an example of the flicker with the carousel. The same flicker happens with the barebones example below.
https://www.loom.com/share/fc726d7ad15b427193c4081d313cf894?sid=5d3ec8ee-842e-4128-9cdf-71542e6726cc
Environment info
Library version: 5.2.1 Device: LG V50S ThinQ Android: version 10
Steps To Reproduce
- Load the component under "Reproducible sample code" which switches between an image and a video when pressed.
- Press to switch between a video and an image
- You'll see the components rendered on top of the video/image not to flicker ...
Reproducible sample code
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Animated, Image, Platform, Pressable, Text, View } from "react-native"
import { StyleSheet } from "react-native"
import Video from "react-native-video"
export default memo(function FeaturedCarouselSlide() {
const [isVideoPausedLocal, setIsVideoPausedLocal] = useState(true)
const onVideoLoaded = useCallback(() => {
setIsVideoPausedLocal(false)
}, [setIsVideoPausedLocal])
const isPaused = isVideoPausedLocal
const poster = "https://dd2cgqlmnwvp5.cloudfront.net/hero-carousel/Team_Hoops_Carousel.jpg"
const [isVideo, setIsVideo] = useState(true)
const videoFormat = Platform.OS === "android" ? "webm" : "mp4"
return (
<Pressable
style={styles.container}
onPress={() => {
setIsVideo(!isVideo)
}}
>
{isVideo ? (
<Video
style={[styles.video]}
paused={isPaused}
source={{
uri: `https://dd2cgqlmnwvp5.cloudfront.net/hero-carousel/Team_Hoops_Carousel.${videoFormat}`,
}}
resizeMode={"cover"}
poster={poster}
posterResizeMode="cover"
repeat
muted
onLoad={onVideoLoaded}
/>
) : (
<View style={[styles.video, { flex: 1 }]}>
<Image style={{ width: "100%", height: "100%", borderRadius: 16 }} source={{ uri: poster }} />
</View>
)}
<View style={styles.overlay} pointerEvents="box-none">
<View style={styles.spaceInfoContainer}>
<Text numberOfLines={2} style={styles.title}>
Title Here
</Text>
<View style={styles.creatorAndStats}>
<Text numberOfLines={1} style={styles.name}>
Name here
</Text>
</View>
</View>
</View>
</Pressable>
)
})
export const styles = StyleSheet.create({
container: {
flex: 1,
borderRadius: 16,
backgroundColor: "transparent",
},
video: {
position: "absolute",
// width: "100%",
// height: "100%",
top: 0,
bottom: 0,
left: 0,
right: 0,
borderRadius: 16,
},
overlay: {
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
width: "100%",
height: "100%",
borderRadius: 16,
},
spaceInfoContainer: {
width: "100%",
position: "absolute",
bottom: 0,
padding: 16,
},
title: {
paddingVertical: 8,
textShadowColor: "rgba(0, 0, 0, 0.15)",
textShadowOffset: {
width: 0,
height: 4,
},
textShadowRadius: 10,
},
creatorAndStats: {
flexDirection: "row",
justifyContent: "space-between",
},
name: {
marginLeft: 4,
maxWidth: 180,
},
})