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

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:
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):
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~

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>

So, I don't know how to make it dynamic to rach video, but you just can't have percentages for height and width

Related

backgroundImage React Native not working with flatlist not showing me the images

i need to show the images on my screen with the element flatlist
const renderItem = ({ item }) => (
<ImageBackground
source={{ uri: item.imageUrl }}
imageStyle={{
borderRadius: 6,
resizeMode: 'contain'
}}
/>
);
return (<FlatList data={coursesData} renderItem={renderItem} keyExtractor={(item) => item.id} />);
I see a few issues:
Why are you using ImageBackground instead of just Image? ImageBackground is intended for use as a background.
If the source of your image is a url you must specify an image height and width.
give Flatlist style={{ flex: 1 }}, this will allow it to take up all the space it needs.

Autoplay video on element focus in react-native

import {Video} from 'expo-av';
return (
<FlatList
data={videos}
// keyExtractor={(item,ind}
keyExtractor={(item) => item.names}
renderItem={({item})=>(
<TouchableOpacity
onPress={() => {console.log('pushed');navigation.push('Details',{url:item.videourl})}}>
<Video
usePoster="true"
source={{ uri: item.videourl }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay={isFocused ? true : false}
// isLooping
// useNativeControls
posterSource={{uri:item.imageurl}}
style={{ height: 300 }}
/>
</TouchableOpacity>
)}/>
);
If one video gets focused then the video must be played and if the video is not focused then it should pause.I am using expo-av for playing video. The above code is playing all videos on the screen but I want to play the video which is focused just like what youtube does.
To do this you need to keep track of how the scrollview has moved (the offset). FlatList has an onScroll property, where the callback is given information about the list layout etc., and you are interested in tracking how much the content has been scrolled vertically - that is contentOffset.y.
Dividing this value by the list item height (a constant 300 in your case) and rounding will give you the index of the item that should be playing.
Use state to store the currently focused index:
const [focusedIndex, setFocusedIndex] = React.useState(0);
Add a handler for the onScroll event :
const handleScroll = React.useCallback(({ nativeEvent: { contentOffset: { y } } }: NativeSyntheticEvent<NativeScrollEvent>) => {
const offset = Math.round(y / ITEM_HEIGHT);
setFocusedIndex(offset)
}, [setFocusedIndex]);
Pass the handler to your list:
<FlatList
onScroll={handleScroll}
...
/>
and modify the video's shouldPlay prop:
<Video
shouldPlay={focusedIndex === index}
...
/>
You can see a working snack here: https://snack.expo.io/#mlisik/video-autoplay-in-a-list, but note that the onScroll doesn't seem to be called if you view the web version.
Try https://github.com/SvanBoxel/visibility-sensor-react-native
Saved my time. You can use it like.
import VisibilitySensor from '#svanboxel/visibility-sensor-react-native'
const Example = props => {
const handleImageVisibility = visible = {
// handle visibility change
}
render() {
return (
<View style={styles.container}>
<VisibilitySensor onChange={handleImageVisibility}>
<Image
style={styles.image}
source={require("../assets/placeholder.png")}
/>
</VisibilitySensor>
</View>
)
}
}

Showing high res images makes my app crash in my react-native app, How can I solve this?

I need to show about 20 high res images in my view with scroll, so when I run my app during the scroll my app will crash in my iphone 6 plus. I checked xcode and find this error message:
[process_info] Exiting because our workspace host has disconnected.
XPC connection interrupted
I had a flat list with lots of imageSlides in it, which each one of them is a 'JourneySlide' component.
here is my code :
export class JourneySlide extends Component {
constructor(props){
super(props);
this.state = {
refreshCount : 0,
}
}
constructViews = () => {
let slideMissions = [];
this.props.levels.forEach((item , index)=> {
slideMissions.push(
<JourneyMission
maxLvlNumber={this.props.levels[0].level_number}
seasonID={this.props.season_id}
is_locked={item.is_locked}
is_completed={item.is_completed}
prize_value={item.prize_value}
prize_type={item.prize_type}
level_number={item.level_number}
message=""
level_id={item.level_id}
onPress={this.props.onPress}
itemIndex={index}
asset_height={this.props.asset_height}
asset_width={this.props.asset_width}
pos_Y={item.pos_Y}
pos_x={item.pos_x}
/>
)
})
return slideMissions;
}
render() {
console.log('Journey Slide render again')
let sizeH = normalize(42);
let {
asset_height,
asset_width,
asset_name,
title,
sub_title,
} = this.props;
let slideHeight = checkHeight(asset_width,asset_height)
return (
<View style={[{height: slideHeight, width : widthx}]}>
{/* {this.constructViews()} */}
<FlatList
data={this.props.levels}
keyExtractor={(item) => item.level_id }
renderItem={({item, index}) =>
<JourneyMission
maxLvlNumber={this.props.levels[0].level_number}
seasonID={this.props.season_id}
is_locked={item.is_locked}
is_completed={item.is_completed}
prize_value={item.prize_value}
prize_type={item.prize_type}
level_number={item.level_number}
message=""
level_id={item.level_id}
onPress={this.props.onPress}
itemIndex={index}
asset_height={this.props.asset_height}
asset_width={this.props.asset_width}
pos_Y={item.pos_Y}
pos_x={item.pos_x}
/>}
showsVerticalScrollIndicator={false}
style={{zIndex:2}}
getItemLayout={(data, index) => (
{length: sizeH, offset: sizeH * index, index}
)}
/>
<Image
style={{
position: 'absolute',
zIndex: 1,
width: widthx,
height: slideHeight
}}
// resizeMode="stretch"
resizeMethod="resize"
source={journey[asset_name]}
/>
</View>
);
}
}
this is where I add each images to my view witch they usually have 2048*2732 and above res
<Image
style={{
position: 'absolute',
zIndex: 1,
width: widthx,
height: slideHeight
}}
// resizeMode="stretch"
resizeMethod="resize"
source={journey[asset_name]}
/>
So my problem is the crash issue during the scrolling, I also checked memory use during scroll, it's about 200 mb and 20% cpu usage during scroll.
I checked this app on newer iphone and it works fine but I need to handle this issue on iphone 6 and 5s
please help me with your advices.
Two suspicious:
(1) I would take a look at (flatlist-performance-tips) for general tips, but in particular look at windowSize. Flatlist will continue rendering a "buffer" of items that are offscreen. In your case, the default values of Flatlist are causing your list to render all your images all the time, once you've scrolled far enough down. Since your images are large, this may be your issue.
(2) How often are your Flatlist items updating? You may not be changing the props/state of the component, but the component may still be doing quite a bit of processing. When I render a static list, I usually extend Purecomponent and then return false on shouldComponentUpdate. This is a huge performance boost in general.

Get coordinates of touch event relative to Image

I have an image centered on the screen. I need to make it touchable and I need to get the coordinates of the touch event relative to the image. I have wrapped my image in a TouchableOpacity to make it touchable. The problem is that the touch coordinates are relative to the TouchableOpacity and not the Image. The TouchableOpacity is taking up the entire screen but the Image is centered inside it.
I either need to make my TouchableOpacity the same size as the Image, or I need to know the offset of the Image within the TouchableOpacity.
I have tried using OnLayout and the NativeEvent object to get the position of the Image within it's parent but it just returns 0,0.
const {width, height} = Dimensions.get("window");
class Inspection extends React.Component {
handlePress(evt) {
// do stuff
...
}
render() {
return (
<View style={{flex:1, backgroundColor:'#fff'}}>
<TouchableOpacity
onPress={(evt) => this.handlePress(evt)}
style={{backgroundColor: '#3897f0'}}
>
<Image
onLayout={({nativeEvent}) => {
console.log(nativeEvent.layout);
}}
source={require('../../images/wireframe-car.jpg')}
resizeMode='contain'
style={{
maxHeight: height,
maxWidth: width
}}
/>
</TouchableOpacity>
</View>
);
}
}
Console Output:
{height: 683.4285888671875, width: 411.4285583496094, y: 0, x: 0}
I've added a backgroundColor to TouchableOpacity so you can see that it takes up the entire screen.
Is there another way of doing this?
TouchableOpacity would be the same size as of the Image because you haven't given any height to it, you simply need to assign onLayout prop to your TouchableOpacity like this
<View
style={{ flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center' }}>
<TouchableOpacity
onLayout={({ nativeEvent }) => {
console.log(nativeEvent.layout)
}}
style={{}}
onPress={evt => this.handlePress(evt)}>
<Image
source={require('../../images/wireframe-car.jpg')}
resizeMode="contain"
style={{
maxWidth: '100%',
}}
/>
</TouchableOpacity>
</View>
This will give you the exact x and y of Image.
Update
The problem is also with image size, since the image size is quite big so it takes the height of the device and we get x:0 and y:0, in order to resolve this issue we can give the Image component a static height or calculate its height according to width. We can get width and height image from local path like this:
let imageUri = Image.resolveAssetSource(require('../../images/wireframe-car.jpg').uri)
Image.getSize(imageUri , (width, height) => {
console.log(`The image dimensions are ${width}x${height}`);
}, (error) => {
console.error(`Couldn't get the image size: ${error.message}`);
});

React Native: Correct scrolling in horizontal FlatList with Item Separator

ReactNative: v0.52.0
Platform: iOS
My FlatList code:
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
ItemSeparatorComponent={this.itemSeparatorComponent}
/>
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
And finally FlatList item component:
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH, height: 'auto' }}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
</View>
)
}
But when scrolling, the FlatList makes an offset to the separator but not to the left edge of item:
And with each new element the FlatList adds the width of the all previous separators to offset:
How to make the FlatList component consider the width of the separator component in horizontal scrolling and make proper offset?
I had the same use-case. For anyone looking for a solution, here it is.
Step 1) Don't use ItemSeparatorComponent prop. Instead, render it inline in your renderItem component.
Step 2) (Key-point). Specify the width and height in the style prop of the FlatList. The width, in your case, should be SCREEN_WIDTH + 5.
Then Flatlist will automatically move the entire screen (photo + separator) away when pagination is enabled. So now your code should be like so:-
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
style={{width: SCREEN_WIDTH + 5, height:'100%'}}
/>
Render photo code:-
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH + 5, height: 'auto',
flexDirection:'row'}}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
{this. itemSeparatorComponent()}
</View>
)}
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
If you still can't figure it out, then look at this component:
https://github.com/zachgibson/react-native-parallax-swiper
Try to go into the implementation, you will see that this guy has provided width and height to the Animated.ScrollView.
https://github.com/zachgibson/react-native-parallax-swiper/blob/master/src/ParallaxSwiper.js
Line number: 93 - 97
The top-level view you're returning in the renderPhoto function has a width of SCREEN_WIDTH, yet the ItemSeparatorComponent, which renders in between each item, is taking up a width of 5 as per your style definition. Consequently, for each additional item you scroll to, that initial offset will become 5 more pixels on the left.
To fix this, you can either remove the ItemSeparatorComponent completely, (as you already have pagingEnabled set to true), or set the width of the top-level view returned in renderPhoto equal to SCREEN_WIDTH - 2.5. That way you'll see half of the item separator on the right edge of one photo and the other half on the left edge of the next photo.
Actually, one other possible solution could be to remove the item separator, set the renderPhoto View's width to SCREEN_WIDTH + 5, and then include these additional properties inside the style: {paddingRight: 5, borderRightWidth: 5, borderRightColor: 'red'}. That way the red separator won't be visible until scrolling left and right, because of the pagingEnabled property.