Search code examples
react-nativeexpoinstagramreact-native-flatlistexpo-av

How to make expo-av video to take needed inside a flatlist?


I am developing an instagram-like app where I needed to render images/videos. I am using flatlist to prevent memory lost, and using expo-av package to render the video.

Here is a screenshot of what I want to achieve: Goal Image

So my goal here is to render videos with original ratio.

However, I am struggling to render the flatlist that contains the videos, it just doesn't render the component at all but I can still hear the video playing.

This is my FlatList:

<FlatList
        style={{ width: "100%", height: "100%", backgroundColor: "yellow" }}
        data={[0, 1, 2, 3, 4]}
        keyExtractor={item => item}
        renderItem={renderItem}
/>

And my renderItem callback:

const renderItem = ({ item }) => {
    return <Post post={item} />;
}

Post item code:

export default ({ post }) => {
    const videoRef = useRef(null);
    const [status, setStatus] = useState({});

    return (
    <View style={{ width: "100%", height: "100%", backgroundColor: "blue" }}>
        <Video
        ref={videoRef}
        source={{
            uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
        }}
        style={{ width: "100%", height: "100%", backgroundColor: "blue" }}
        resizeMode="contain"
        autoplay
        isLooping
        shouldPlay={true}
        onPlaybackStatusUpdate={status => setStatus(() => status)}
        />
    </View>
    );
}

Result (yellow background: flatlist area, post item should appear blue but not showing up): Result Image

The video would display if I give it a static width and height instead of values like 100%, but since I needed the renderItem to look original and take as much space as needed across all kinds of devices, so the only thing I could think of is a percentage.

If there is a way to know the aspect ratio or the width and height of the video, I can do dynamic calculations to achieve my goal, but I don't know if expo-av provide this information~


Solution

  • The renderItem would automatically take the max width inside a FlatList, but the height is default 0. I figured that we can pass the video's natural aspect ratio to the style property so that it renders itself naturally with max size.

    To get the video's natural aspect ratio, we have to define a function for the video's onReadyForDisplay property, it provides information of the video once the first frame is loaded.

    To do that, we set the default ratio to the screen ratio:

    import { Dimensions } from "react-native";
    
    const defaultScreenRatio = Dimensions.get("window").width / Dimensions.get("window").height;
    

    And then inside the component:

    // Use the screenDefaultRatio to render the video before the video is loaded
    const [videoRatio, setVideoRatio] = useState(screenDefaultRatio);
    
    // Update the videoRatio right after we know the video natural size
    const updateVideoRatioOnDisplay = (videoDetails) => {
        const { width, height } = videoDetails.naturalSize;
        const newVideoRatio = width / height;
    
        setVideoRatio(newVideoRatio);
    }
    

    Code for Video item:

    <View>
        <Video
        ref={videoRef}
        source={{
            uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
        }}
        style={{ aspectRatio: videoRatio, backgroundColor: "blue" }}
        resizeMode="contain"
        autoplay
        isLooping
        shouldPlay={true}
        onPlaybackStatusUpdate={status => setStatus(() => status)}
    
        // Update the video Ratio once done loading the first frame of the video
        onReadyForDisplay={updateVideoRatioOnDisplay}
        />
    </View>