I'm working on a React Native app that includes a TikTok-like feed with images and sound. The core components involved are a FlatList for displaying posts and a PagerView for swiping through multiple images within each post. The problem arises when users interact with the feed quickly, switching between images in the PagerView or scrolling through the FlatList rapidly.
The sound playback associated with each post becomes unreliable in these scenarios. Sometimes, the same sound starts playing multiple times, causing a confusing and unpleasant user experience. Additionally, I'm encountering a performance warning related to the use of the VirtualizedList component: "VirtualizedList: You have a large list that is slow to update."
Below is a simplified excerpt from the relevant parts of the code to highlight the setup and interactions causing the issues:
import React, { Component, PureComponent } from 'react';
import { View, Image, Text, FlatList, Dimensions, ActivityIndicator, Pressable } from 'react-native';
import { Audio } from 'expo-av';
import PagerView from 'react-native-pager-view';
import FastImage from 'react-native-fast-image';
import debounce from 'lodash.debounce';
const screenWidth = Dimensions.get('window').width;
const screenHeight = Dimensions.get('window').height;
const COOLDOWN_TIME_MS = 50; // Cooldown-Zeit für die Sound-Wiedergabe in Millisekunden
class TikTokImage extends PureComponent {
// State-Management für das Laden der Bilder
constructor(props) {
super(props);
this.state = {
loading: true,
progress: 0,
};
}
render() {
const { image } = this.props;
return (
<View style={{ width: screenWidth, height: screenHeight - 50 }}>
<FastImage
source={{ uri: image }}
style={{
flex: 1,
width: screenWidth,
}}
resizeMode={FastImage.resizeMode.cover}
placeholderStyle={{ backgroundColor: 'transparent' }}
placeholderContent={<View style={{ flex: 1, backgroundColor: 'lightgray' }} />}
// fallback
fallback={<ActivityIndicator />}
/>
</View>
);
}
}
class TikTokLikeComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
currentImageIndices: [],
loading: false,
page: 1,
sound: null,
currentSoundIndex: -1,
lastSoundPlayTime: 0,
};
this.imagePagerRefs = [];
this.flatListIndex = -1;
this.debouncedPlaySound = debounce(this.playSound, COOLDOWN_TIME_MS);
}
async playSound(soundUrl, index) {
const now = Date.now();
// Überprüfen, ob genügend Zeit seit der letzten Sound-Wiedergabe vergangen ist und wenn die seite im fokus ist
if (now - this.state.lastSoundPlayTime >= COOLDOWN_TIME_MS && this.props.navigation.isFocused()) {
// Sicherstellen, dass der vorherige Sound vollständig entladen wird
await this.unloadSound();
// Sound erstellen und initialisieren
const { sound } = await Audio.Sound.createAsync(
{ uri: soundUrl },
{ shouldPlay: true }
);
// Laustärke auf 50% setzen und Sound auf Loop setzen
await sound.setVolumeAsync(0.5);
await sound.setIsLoopingAsync(true);
// Wenn der User den Stack oder den Screen wechselt, wird der Sound automatisch entladen
this.props.navigation.addListener('blur', async () => {
// Wenn ein song läuft, dann stoppen
if (this.state.sound) {
await this.state.sound.pauseAsync();
}
});
this.props.navigation.addListener('focus', async () => {
// Wenn kein song läuft und der Sound nicht null ist, dann wieder abspielen
if (soundUrl !== null) {
await this.state.sound.playAsync();
}
});
// Zustand aktualisieren
this.setState({ sound, currentSoundIndex: index, lastSoundPlayTime: now });
}
}
async unloadSound() {
if (this.state.sound) {
await this.state.sound.unloadAsync();
this.setState({ sound: null, currentSoundIndex: -1 });
}
}
async unloadAllSounds() {
await Audio.Sound.stopAsync();
await Audio.Sound.unloadAsync();
this.setState({ sound: null, currentSoundIndex: -1 });
}
// Other methods and lifecycle hooks...
onPageSelected = (postIndex, event) => {
const newIndices = [...this.state.currentImageIndices];
newIndices[postIndex] = event.nativeEvent.position;
this.setState({ currentImageIndices: newIndices });
};
onViewableItemsChanged = async ({ viewableItems }) => {
const firstVisibleIndex = viewableItems[0]?.index;
if (firstVisibleIndex !== undefined && firstVisibleIndex !== this.flatListIndex) {
this.flatListIndex = firstVisibleIndex;
const soundUrl = this.state.data[firstVisibleIndex]?.sound?.songUrl;
if (soundUrl) {
if (this.state.currentSoundIndex !== firstVisibleIndex) {
await this.unloadSound();
this.debouncedPlaySound(soundUrl, firstVisibleIndex);
}
} else if (!soundUrl && this.state.sound) {
await this.unloadSound();
}
}
};
renderItem = ({ item, index }) => {
const postIndex = index;
const totalImages = item.imageUrls.length;
return (
<View style={{ flex: 1 }}>
<PagerView
style={{ flex: 1, width: screenWidth, height: screenHeight - 50 }}
initialPage={0}
ref={ref => (this.imagePagerRefs[postIndex] = ref)}
onPageSelected={event => this.onPageSelected(postIndex, event)}>
{item.imageUrls.map((image, imageIndex) => (
<TikTokImage key={imageIndex} image={image} />
))}
</PagerView>
// Rest of code
</View>
);
});
// Other methods...
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
// FlatList props...
onViewableItemsChanged={this.onViewableItemsChanged}
/>
{/* Bottom-Leiste here */}
</View>
);
}
}
export default TikTokLikeComponent;
What specific changes or approaches can I use to prevent multiple sound playback and improve VirtualizedList performance while using a FlatList and PagerView for image and sound content in a TikTok-like feed? I've tried debouncing sound playback and optimizing the FlatList's rendering behavior, but the issues persist.
I'd appreciate any insights or suggestions to help resolve these issues and improve the overall user experience in my React Native app. Thank you!
I've attempted various strategies to address the issues of multiple sound playback and the VirtualizedList performance warning in my React Native app's TikTok-like feed. Here's what I've tried and the expected outcomes:
Debounced Sound Playback: I implemented a debounce function to manage sound playback to avoid rapid, unintended plays when users interact quickly. I expected this to ensure that only one sound plays during the cooldown period, providing a smoother audio experience.
Unload Sound Before Playback: I unload the previous sound before playing a new one to prevent overlapping sound playback. My expectation was that unloading the previous sound before initiating a new one would result in consistent and single sound playback.
Optimized FlatList Rendering: I've experimented with various FlatList props such as initialNumToRender, maxToRenderPerBatch, and windowSize to optimize rendering performance. I aimed to minimize unnecessary re-renders and improve scrolling performance.