I have a scrollview that has as a child an array of video components. It displays on the UI a carousel of videos. I would like the user to be able to click a play button that is above the video and play that video, but the way my logic is set, all the videos play together because the condition to play the video lives in a react useState.
see the code:
const VideosView = (props) => {
const videoClips = props.route.params.data;
const [state, setState] = React.useState(0);
const [play, setPlay] = React.useState(false);
return (
<>
<SafeAreaView pointerEvents='box-none' style={styles.root}>
<Header
goBack={() => props.navigation.goBack()}
title={state === 0 ? 'RYGGRöRLIGHET' : 'STYRKA & BALANS'}
/>
<View style={styles.paragraphsContainer}>
<Text style={styles.title}>
{state === 0 ? 'Ryggrörlighet' : 'Styrka & Balans'}
</Text>
<Text style={styles.paragraph}>
'Utför övningen i ca 5 min för bästa resultat.'
</Text>
<View style={styles.circlesContainer}>
{renderImages.map((_, index) => {
return (
<TouchableOpacity key={index} onPress={() => setState(0)}>
<View
style={[
styles.circles,
{ opacity: index === state ? 1 : 0.5 }
]}
/>
</TouchableOpacity>
);
})}
</View>
</View>
</SafeAreaView>
<View style={styles.scrollViewContainer}>
<ScrollView
bounces={false}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={30}
onScroll={({ nativeEvent }) => {
const slide = Math.ceil(
nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width
);
if (slide !== state) {
setState(slide);
}
}}
horizontal>
{videoClips.map((video, index) => (
<View style={{ position: 'relative' }}>
{!play && (
<TouchableOpacity
style={{
position: 'absolute',
backgroundColor: 'rgba(255,255,255,0.5)',
alignSelf: 'center',
top: 130,
width: 160,
height: 160,
borderRadius: 500,
zIndex: 4000
}}
onPress={() => setPlay(true)}></TouchableOpacity>
)}
<Video
shouldPlay={play}
key={index}
source={{
uri: video
}}
resizeMode='cover'
style={{ width: wp('100%'), height: hp('100%') }}
/>
</View>
))}
</ScrollView>
</View>
</>
);
};
I would like the user to click a button and play one video, use the carousel to go to next video, click play and play the next video.
Please, help.
If you change the play state from Boolean to Number you will probably solve your problem. Here is how:
const [videoBeingDisplayedIndex, setVideoBeingDisplayedIndex] = React.useState(5);
const [play, setPlay] = React.useState(5); // If it is equals to the video id the video should play, else it should not
...
<Button onClick={() => setPlay(videoBeingDisplayedIndex)}>
Play Video
</Button>
<VideoCarousel>
{
myVideoList.map((video, index) => {
return (
<Video
content={video}
index={index}
play={play === index} // Check if the current play state is equals to the video unique id
/>
);
})
}
</VideoCarousel>
...
PS.: please, have in mind that this snippet is abstracting the react-native elements and focusing on the problem.
Related
Here is my code:
const Success =({navigation})=>{
useEffect(() => {
setTimeout(()=>{
<View>
<Image source={images.done}
style={{
width:100,
height:100
}}
/>
</View>
navigation.navigate('Home')
},4000)
}, [])
return(
<View style={{
flex:1,
justifyContent:'center',
alignItems:'center',
}}>
<LottieView
style={{
width: 100,
height: 60,
}}
source={images.progress}
autoPlay
/>
</View>
)
}
export default Success ;
Please Help me out How I can display first Lotti progress and then immediately I wanna the image done appears after the Lotti progress finish and disappear. so I would know how to set time for Lotti progress and then set time to display the Image done then navigate to the next page
You can make use of onAnimationFinish props in lottie-react-native library. Tell the program what you are going to do when the lottie animation end.
const Success = ({navigation}) => {
const [lottieFinished, setLottieFinished] = useState(false);
const onAnimationFinish = () => {
setLottieFinished(true);
setTimeout(() => {
//Go Back to home page after 4 seconds
navigation.navigate('Home')
}, 4000);
}
return (
<View>
{
!lottieFinished ?
<LottieView
source={images.progress}
autoPlay
loop={false}
onAnimationFinish={onAnimationFinish}
/>
:
<Image
source={images.done}
style={{height:100, width:100}}
/>
}
</View>
);
}
I want it to play only when the card is selected, if the card is not selected it simply displays the lottie file but nothing is being played. Right now I get an error of cannot read property 'play' of null. When I remove the animationPress() and get the file to run it also start at the mentioned initialSegment that I'm passing into it.
What am I doing wrong here?
My card that the Lottie file is in:
const animation = useRef(null);
const animationPress = () => {
animation.current.play();
}
return(
{filteredData.map((item, index) => {
return (
<>
<TouchableOpacity onPress={() => setSelected(item), animationPress()}>
<View style={[{ alignItems: 'center' }, selected.id == item.id ? { paddingTop: 10 } : { paddingTop: 20 }]}>
<CardioCard style={selected.id == item.id ? { backgroundColor: 'green', fontFamily: 'ssb_SemiBold' } : {}} >
<CardItem style={[styles.card, selected.id == item.id ? { backgroundColor: 'green' } : {}]}>
<Text>
< LottieView
ref={animation}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={true}
loop={true}
style={{ width: 100, height: 100 }} />
</Text>
</CardItem>
</CardioCard>
<Text style={[{ fontSize: 18, color: 'hsl(98, 0%, 11%)', paddingLeft: 15 }, selected.id == item.id ? { fontFamily: 'ssb_SemiBold' } : {}]}>{item.name}</Text>
</View>
</TouchableOpacity>
</>
)
})}
...
)
Edit
I also tried to do useState for the autoplay and loop button so they would be false but when the button is pressed it would turn true but the animation wouldn't load even after the button press.
const [playAnimation, setPlayAnimation] = useState(false);
<TouchableOpacity onPress={() => setPlayAnimation(true)}>
<LottieView
source={require('../../assets/images/heartbeat.json')}
autoPlay={playAnimation}
loop={playAnimation}
style={{ width: 100, height: 100 }}
/>
</TouchableOpacity>
this is driving me nuts and idk what to do anymore.
I'm new to react-native but I guess it's like react. Not sure to correctly understand the issue.
In this case, shouldn't you use animation.current instead of animation as your lottieView ref? or at least ensure that your ref animation is defined (not null) so that the container can be mounted even if not launched, this is also for your animationPress function. Also you should disable the autoPlay I guess (since you want to trigger the animation with a button).
< LottieView
ref={animation.current}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={false}
loop={true}
style={{ width: 100, height: 100 }} />
or
{animation && < LottieView
ref={animation}
source={require('../../assets/images/heartbeat.json')}
initialSegment={68,135}
autoPlay={false}
loop={true}
style={{ width: 100, height: 100 }} />}
And of course
const animationPress = () => {
if(animation?.current) {
animation.current.play();
}
}
im building a music-player and i want to implement this:
at the bottom, theres a bottomsheet, so if I go to albums, artist, it will still there, how can i do it? for now i have but only in one screen, in this case Tracks:
render ()
const props = {
playing: this.state.playing,
update_playing: this.update_playing.bind(this),
update_next_song: this.update_next_song.bind(this),
repeat_song: this.repeat_song.bind(this),
repeat_queue: this.repeat_queue.bind(this),
show_hide_icon: this.show_hide_icon.bind(this),
current_track_id: this.state.current_track_id,
current_track: (this.state.current_track === "") ? this.props.info_track[0]?.title : this.state.current_track,
cover: (this.state.current_track === "") ? this.props.info_track[0]?.cover : this.state.cover,
artist: (this.state.current_track === "") ? this.props.info_track[0]?.artist : this.state.artist,
show_icon: this.state.show_icon,
tracks: this.props?.tracks,
first_id_song: this.props?.first_id_song,
last_id_song: this.props?.last_id_song,
}
return (
<>
<View style = {{flex: 1, backgroundColor: "black"}}>
<Text style = {{fontSize: 30, color: "red"}}>{saludo}</Text>
<FlatList
data = {this.props.tracks}
keyExtractor = {(item) => item.id.toString()}
renderItem = {({item}) => (
<TouchableOpacity onPress = {() => {this.play_selected_music(item) ; this.get_track_cover_artist(item)}}>
<View style = {this.styles.tracks_container}>
<View style = {this.styles.tracks_info_container}>
{
(item?.cover) ? <Image source = {{uri:"file:///"+item.cover}} style={{ width: 100, height: 100, marginLeft: 20}}></Image>
: <Image source = {require("../assets/default_image.jpg")} style={{ width: 100, height: 100, marginLeft: 20}}></Image>
}
<View style = {this.styles.tracks}>
<View>
<Text style = {{color: "white", fontSize: 20, marginLeft: 10}} numberOfLines = {1}>
{
(item.title.length > 20) ?
item.title.substring(0,18).padEnd(20,".")
: item.title
}
</Text>
</View>
<View style = {this.styles.artist_duration}>
<Text style = {{color: "white", fontSize: 10, marginLeft: 10}}>
Artist: {(item.artist.length > 15) ?
item.artist.substring(0,14).padEnd(16,".")
: item.artist}
</Text>
<Text style = {{color: "white",fontSize: 10, marginLeft: 10}}>
Duration: {this.get_time(item.duration)}
</Text>
</View>
</View>
</View>
</View>
</TouchableOpacity>
)}
>
</FlatList>
<BottomSheet
ref = {ref => (this.sheetref = ref)}
initialSnap = {1}
snapPoints = {[Dimensions.get("window").height - StatusBar.currentHeight, 95]}
renderHeader = {() => <Player_Header {...props}></Player_Header>}
renderContent = {() => <Track_Zone {...props}></Track_Zone>}
enabledContentGestureInteraction = {false}
onOpenEnd = {this.hide_icon}
onCloseEnd = {this.show_icon}>
</BottomSheet>
</View>
</>)
it receives props from the component to get update everytime the track changes, thought about putting it outside the navigator, but then, how can get all the necessary props, functions, etc to update it?
this is my navigator:
<Tab.Navigator>
<Tab.Screen name = "Tracks" children = {({navigation}) => <Tracks navigation = {navigation}></Tracks>}></Tab.Screen>
<Tab.Screen name = "Chao" children = {({navigation}) => <Chao navigation = {navigation}></Chao>}></Tab.Screen>
</Tab.Navigator>
You can define a HOC to render the BottomSheet withTracKBottomSheet()
Now you can wrap every screen in which there should be a BottomSheet with withTrackBottomSheet().
Something like this
const withTrackBottomSheet = Component => {
// Do all the business logic
return (
<>
<Component />
<BottomSheet />
</>
);
};
In your case, the same state will be shared among multiple screens/components. Thus I will advise you to use some state management library like redux to make your work a little easier and less complex.
Hi I'm trying to get an youtube player using react-native-youtube but I'm getting a black screen with an endless loading in android. It works well in iOS. Does someone have an idea why ?
Here is the code :
<View>
<YouTube
apiKey="APIKEY"
videoId={this.state.videoUrls} // The YouTube video ID
play = {this.state.isPlaying} // control playback of video with true/false
fullscreen = {this.state.fullscreen} // control whether the video should play in fullscreen or inline
loop = {this.state.isLooping}// control whether the video should loop when ended
onReady={e => this.setState({ isReady: true })}
onChangeState={e => this.setState({ status: e.state })}
onChangeQuality={e => this.setState({ quality: e.quality })}
onError={e => this.setState({ error: e.error })}
style={{ alignSelf: 'stretch', height: 250 }}
/>
</View>
EDIT : the code of the videoId
<View style = {styles.containerPlaylist}>
<View>
{
this.state.dataVideos.map((item,i) =>
<TouchableHighlight
key = {item.contentDetails.videoId}
onPress = {()=> this.setState({videoUrls: item.contentDetails.videoId})}>
<View style = {styles.vids}>
<Image
source = {{uri: item.snippet.thumbnails.medium.url}}
style = {{flex: 2, height: '100%', backgroundColor:'#fff', resizeMode:'contain'}}
/>
<Text style = {styles.vidText}>{item.snippet.title}</Text>
</View>
</TouchableHighlight>
)}
</View>
</View>
I'm mapping TouchableOpacity with an Image nested inside of it. It works great on Android but on iOS the image is invisible. There is still a 75x75 touchable opacity that I can tap but the image is invisible in the modal that pops up and just in general.
How does this work?
I'm using Expo SDK FileSystem to get the path of each image.
For example: file://path/to/container/progress/myfilehash.jpg
I push this to my state and map it in the component. the require() function WILL NOT WORK for the way I am doing this. I think it is purely a problem with the rendering.
Map code:
{this.state.images.map((val, key) => (
<TouchableOpacity
key={key}
onPress={() => this.setState({active: val, modal: true})}
>
<Image
style={{width: 75, height: 75}}
source={{isStatic: true, uri: val}}
/>
</TouchableOpacity>
))}
Modal:
<Container style={Colors.Backdrop}>
<Header style={Colors.Navbar}>
<Left>
<TouchableHighlight
onPress={() => {
this.setState({modal: false})
}}>
<Icon
name="arrow-back"
style={{color: 'white'}}
/>
</TouchableHighlight>
</Left>
<Body></Body>
<Right>
<TouchableOpacity
onPress={() => {
this._deleteImage(this.state.active);
}}>
<Text
style={[
Colors.ErrorText, {
fontSize: 24,
marginRight: 10
}
]}>×</Text>
</TouchableOpacity>
</Right>
</Header>
<Content>
<View
style={{flex: 1}}
>
<FitImage
source={{uri: this.state.active}}
/>
</View>
</Content>
</Container>
Code for fetching image paths. (NOTE: I tried not truncating "file://" from ios with same exact result)
_getAllImagesInDirectory = async() => {
let dir = await FileSystem.readDirectoryAsync(FileSystem.documentDirectory + 'progress');
dir.forEach((val) => {
this.state.images.push(Platform.OS === 'ios' ? FileSystem.documentDirectory.substring(7, FileSystem.documentDirectory.length) : FileSystem.documentDirectory + 'progress/' + val);
});
await this.setState({images: this.state.images, loading: false});
}
I also faced this problem once, I solved it by pasting the image in the root folder and then used that path in the source of Image tag.