ПІДТРИМАЙ УКРАЇНУ ПІДТРИМАТИ АРМІЮ
Uk Uk

Creating a TikTok-like Scrolling Video Feed with React Native Expo

Creating a TikTok-like Scrolling Video Feed with React Native Expo

Before we dive into the article, let's preview a demonstration video of the Tiktok-esque video feed...

Before we dive into the article, let's preview a demonstration video of the Tiktok-esque video feed that we are about to build. The demo video shows a feed with smooth scrolling, auto-play and pause functionality. Pay attention to how videos automatically play when in focus and pause when not, and the full-screen playback for an immersive experience.

Demo

Prerequisite:Ensure that you have a React Native Expo project already set up. This is a fundamental requirement for following along with the tutorial.

1. Install expo-av

expo-av is an Expo module specifically designed for audio and video playback in React Native Expo apps. It's vital for our project because it allows for smooth video playback and control, ensuring an engaging user experience on both iOS and Android platforms.

To set up, simply run

npx expo install expo-av

2. FeedScreen component

You can name this component whatever you prefer.

import React from 'react'

const FeedScreen = () => {
 return (
 <>
 </>
 )
}

export default FeedScreen

3. Import Modules

import { View, Dimensions, FlatList, StyleSheet, Pressable } from 'react-native';
import { Video, ResizeMode } from 'expo-av';
import React, { useEffect, useRef, useState } from 'react';

Video , ResizeMode from expo-av :

These will handle video playback. Video is the component for displaying videos, and ResizeMode controls how the video fits into its container.

4. Video Data Array

You can copy the videos array below or copy from this list of public video urls here .

const videos = [
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
];

5. State Management and Viewability

const [currentViewableItemIndex, setCurrentViewableItemIndex] = useState(0);
const viewabilityConfig = { viewAreaCoveragePercentThreshold: 50 }
const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }])

currentViewableItemIndex

Keeps track of which video is currently in the user's view. This is crucial for knowing which video to play.

viewabilityConfig

Defines what counts as a "viewable" item. Here, an item covering more than 50% of the screen is considered viewable. This threshold ensures that the video in the main focus of the screen is the one that plays.

viewabilityConfigCallbackPairs

Manages how videos in the feed are played and paused based on their visibility on the screen.

6. Viewable Items Change Handler

 const onViewableItemsChanged = ({ viewableItems }: any) => {
 if (viewableItems.length > 0) {
 setCurrentViewableItemIndex(viewableItems[0].index ?? 0);
 }
 }

This function updates currentViewableItemIndex based on the item currently in view. It's essential for determining which video should be playing as the user scrolls.

7. FlatList for Rendering Videos

return (
 <View style=>
 <FlatList
 data=
 renderItem={({ item, index }) => (
 <Item item= shouldPlay= />
 )}
 keyExtractor=
 pagingEnabled
 horizontal=
 showsVerticalScrollIndicator=
 viewabilityConfigCallbackPairs=
 />
 </View>
 );

pagingEnabled

When set to true, the list allows snapping to items (paging) as you scroll, creating a carousel-like effect. This is similar to how feeds work in social media apps.

viewabilityConfigCallbackPairs

This prop links our viewability configuration and callback function to the FlatList. It's crucial for detecting which video is in the viewport and should thus be playing.

8. The Item Component

const Item = ({ item, shouldPlay }: ) => {
 const video = React.useRef<Video | null>(null);
 const [status, setStatus] = useState<any>(null);

 useEffect(() => {
 if (!video.current) return;

 if (shouldPlay) {
 video.current.playAsync()
 } else {
 video.current.pauseAsync()
 video.current.setPositionAsync(0)
 }
 }, [shouldPlay])

 return (
 <Pressable onPress={() => status.isPlaying ? video.current?.pauseAsync() : video.current?.playAsync()}>
 <View style=>
 <Video 
 ref=
 source={{ uri: item }}
 style=
 isLooping
 resizeMode=
 useNativeControls=
 onPlaybackStatusUpdate=
 />
 </View>
 </Pressable>
 );
}

Item component detailed breakdown

const video = React.useRef<Video | null>(null);

This reference allows you to control the video's playback programmatically (like playing or pausing). useRef is used instead of a state variable because we need a way to persistently access the video component without causing re-renders.

const [status, setStatus] = useState<any>(null);

This state holds the playback status of the video (like whether it's playing, paused, buffering, etc.). It's updated whenever the playback status of the video changes.

useEffect(() => {
 if (!video.current) return;
 if (shouldPlay) {
 video.current.playAsync();
 } else {
 video.current.pauseAsync();
 video.current.setPositionAsync(0);
 }
}, [shouldPlay]);

This useEffect is triggered whenever the shouldPlay prop changes. It checks if the video is supposed to be playing. If so, it starts playback; otherwise, it pauses the video and resets its position to the start.

<Pressable onPress={() => status.isPlaying ? video.current?.pauseAsync() : video.current?.playAsync()}>

Wraps the video component to make it interactive. When the user taps the video, it toggles between playing and pausing.

<Video 
 ref=
 source={{ uri: item }}
 style=
 isLooping
 resizeMode=
 useNativeControls=
 onPlaybackStatusUpdate=
/>

isLooping : If true, the video will loop continuously.

resizeMode : Determines how the video fits within the bounds of its container.

useNativeControls : Set to false to hide native playback controls.

onPlaybackStatusUpdate : A callback function that updates the status state whenever the playback status of the video changes.

9. Styles

const styles = StyleSheet.create({
 container: {
 flex: 1,
 },
 videoContainer: {
 width: Dimensions.get('window').width,
 height: Dimensions.get('window').height,
 },
 video: {
 width: '100%',
 height: '100%',
 },
});

Full Code

import { View, Dimensions, FlatList, StyleSheet, Pressable } from 'react-native';
import { Video, ResizeMode } from 'expo-av';
import React, { useEffect, useRef, useState } from 'react';

const videos = [
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
 "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4",
];

export default function FeedScreen() {
 const [currentViewableItemIndex, setCurrentViewableItemIndex] = useState(0);
 const viewabilityConfig = { viewAreaCoveragePercentThreshold: 50 }
 const onViewableItemsChanged = ({ viewableItems }: any) => {
 if (viewableItems.length > 0) {
 setCurrentViewableItemIndex(viewableItems[0].index ?? 0);
 }
 }
 const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }])
 return (
 <View style=>
 <FlatList
 data=
 renderItem={({ item, index }) => (
 <Item item= shouldPlay= />
 )}
 keyExtractor=
 pagingEnabled
 horizontal=
 showsVerticalScrollIndicator=
 viewabilityConfigCallbackPairs=
 />
 </View>
 );
}

const Item = ({ item, shouldPlay }: ) => {
 const video = React.useRef<Video | null>(null);
 const [status, setStatus] = useState<any>(null);

 useEffect(() => {
 if (!video.current) return;

 if (shouldPlay) {
 video.current.playAsync()
 } else {
 video.current.pauseAsync()
 video.current.setPositionAsync(0)
 }
 }, [shouldPlay])

 return (
 <Pressable onPress={() => status.isPlaying ? video.current?.pauseAsync() : video.current?.playAsync()}>
 <View style=>
 <Video 
 ref=
 source={{ uri: item }}
 style=
 isLooping
 resizeMode=
 useNativeControls=
 onPlaybackStatusUpdate=
 />
 </View>
 </Pressable>
 );
}

const styles = StyleSheet.create({
 container: {
 flex: 1,
 },
 videoContainer: {
 width: Dimensions.get('window').width,
 height: Dimensions.get('window').height,
 },
 video: {
 width: '100%',
 height: '100%',
 },
});

That’s all!

Ресурс : dev.to


Scroll to Top