Expo Video re-render on state change - react-native

I have a video where all I want it a play button over the top. I have achieved this with the following:
<View style={{ backgroundColor: 'yellow', justifyContent: 'center', alignItems: 'center'}}>
<Video
ref={setupVideo}
source={require('../../assets/AppSampleCoverVideo.mp4')} // TODO:
rate={1.0}
volume={1.0}
isMuted={false}
shouldPlay={isPlaying}
resizeMode='contain'
isLooping={false}
style={styles.setupVideo}
//onPlaybackStatusUpdate={onPlaybackStatusUpdate}
//useNativeControls={true}
/>
<TouchableOpacity style={styles.playButton} onPress={toggleVideoPlay}>
{
!isPlaying ? (
<Ionicons style={styles.playIcon} color='white' size={ Dimensions.get('window').width / 5 } name='ios-play' />
) : null
}
</TouchableOpacity>
</View>
I control the video with the following:
const [isPlaying, setIsPlaying] = useState(false)
const setupVideo = useRef(null);
const toggleVideoPlay = async () => {
try {
let videoStatus = await setupVideo.current.getStatusAsync();
if(videoStatus.isPlaying) {
return setupVideo.current.pauseAsync();
} else {
if(videoStatus.durationMillis === videoStatus.positionMillis) return setupVideo.current.replayAsync();
return setupVideo.current.playAsync();
}
} catch(err) {
}
}
However, I want to be able to track when the video is playing and when it is not in order to set the isPlaying and control whether the play button icon is rendered or not. Anything I have tried causes either the video to flicker when calling setIsPlaying or the video re-renders and doesn't play at all.
Any help would be appreciated.

Related

Navigation using images nested inside Touchable Opacity

Background:
I've designed a custom footer for my app in React Native, I've set some images to act as icons. I'm trying to have them redirect to other pages of the app upon touch.
What I have tried
I've been trying to use the same images nested within TouchableOpacity components to have them redirect to other pages using react navigation.
This is my code:
export class Footer extends React.Component {
render (){
return (
<View style = { styles.footStyle } >
<TouchableOpacity onPress={ () => navigation.push('Home')} >
<Image
style = { styles.iconStyle }
source = {require('./img/home.png')}/>
</TouchableOpacity>
<TouchableOpacity onPress={ () => navigation.push('Favoritos')} >
<Image
style = { styles.iconStyle }
source = {require('./img/heart.png')}/>
</TouchableOpacity>
<TouchableOpacity onPress={ () => navigation.push('Search')} >
<Image
style = { styles.iconStyle }
source = {require('./img/search.png')}/>
</TouchableOpacity>
<TouchableOpacity onPress={ () => navigation.push('Notifications')} >
<Image
style = { styles.iconStyle }
source = {require('./img/bell.png')}/>
</TouchableOpacity>
<TouchableOpacity onPress={ () => navigation.push('Help')} >
<Image
style = { styles.iconStyle }
source = {require('./img/circle.png')}/>
</TouchableOpacity>
</View>
)
}
}
const styles = StyleSheet.create({
footStyle: {
paddingBottom: 0,
paddingRight: 10,
backgroundColor: '#ffffff',
flex: 0.4,
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: '#000000'
},
iconStyle: {
flex: 0.2,
height: undefined,
width: undefined
}
})
Problem
When I try and run the app in expo, the images are not rendering at all. I get my blank footer without any content. I've tried touching the footer to see if the images weren't rendering but the "button" actually worked, that didn't work.
Question
How exactly can I nest an image within a TouchableOpacity component? Is it even possible to use this method with React Navigation?
Thanks a lot!
For an Image component to work you should provide a height and width in style.
Here you are setting it as undefined
Try something like
iconStyle: {
flex: 0.2,
height: 100,
width: 100
}
Also on the navigation, you will have to pass the navigation prop to the Footer. As its a class you should access it as this.props.navigation.navigate()
As your code for integrating the Footer is not here, its hard to comment on how to pass the prop to the footer.

How do I pause a playing video (played with react-native-video) when user scrolls to another page in a viewpager (#react-native-community/viewpager)?

In my app I am using a ViewPager (#react-native-community/viewpager) to display several pages. Some of these pages contain videos that I am displaying using react-native-video. If I start a video on one page and then scroll to the next page the video keeps playing in the background. The user can hear the audio of the video although the audio is not visible.
I would like to pause the video when the user scrolls to the next page, how do I accomplish that?
You can store the current page value in state and set some indexes to your pages, like in example below. After that just check if your page is current page:
<ViewPager
initialPage={0}
onPageScroll={({ nativeEvent }) => {
this.setState({
activePage: nativeEvent.position
})
}
}>
<View key="1">
<Video source={{ uri: '' }}
paused={this.state.activePage !== 0} />
</View>
<View key="2">
<Video source={{ uri: '' }}
paused={this.state.activePage !== 1} />
</View>
</ViewPager>
EDIT:
If you do not need the video to auto-start, you can add isPlaying property in state, and set it to true when you need.
<ViewPager
initialPage={0}
onPageScroll={({ nativeEvent }) => {
this.setState({
activePage: nativeEvent.position,
isPlaying: false
})
}
}>
<View key="1">
<Video source={{ uri: '' }}
paused={!this.state.isPlaying || this.state.activePage !== 0} />
</View>
<View key="2">
<Video source={{ uri: '' }}
paused={!this.state.isPlaying || this.state.activePage !== 1} />
</View>
</ViewPager>
You can check index of ViewPage with index of VideoItem when you map data.
Example:
<ViewPager
onPageSelected={this.onPageSelected}
initialPage={currentVisibleIndex}
style={{ width: '100%', height: '100%', justifyContent: 'center' }}
key={data.length}
// onPageScroll={this.onPageScroll}
scrollEnabled={data && data.length > 1}
// onPageScrollStateChanged={this.onPageScrollStateChanged}
>
{data.map((item, i) => {
if (isVideo(item)) {
return (
<VideoItem
uri={item}
key={i.toString()}
currentVisibleIndex={currentVisibleIndex}
currentIndex={i}
/>
);
}
return (
<ImageItem
uri={item}
defaultSource={Images.defaultImageAlbum}
key={i.toString()}
/>
);
})}
</ViewPager>
And in VideoItem.js you can use this to paused video when you scroll viewpager.
static getDerivedStateFromProps(props, state) {
let { paused } = state;
if (props.currentVisibleIndex !== props.currentIndex) {
paused = true;
}
return {
paused,
currentVisibleIndex: props.currentVisibleIndex
};
}
Sorry about my English.

Pause video when new video plays react-native-video play

I have several Video players on a page.. would like to play only one video at a time so when one is playing, any other that was playing before stops... The Idea I had is to identify the ID of the video that has been tapped on then let this be the only video that is allowed to play so whatever was playing before should be paused. I am not sure of how to do this...
const MediaItem =({result}) => {
const [currentVideoPlayingId, setCurrentVideoPlayingId] = useState(' ')
const oneVideoPlaying = ()=>{
setCurrentVideoPlayingId(result.id)
}
console.log('currentVideoPlaying Id:',currentVideoPlayingId)
return (
<View style={styles.Container}>
<Card style={styles.CardView}>
<View style={styles.VideoItem}>
<VideoPlayer
video= {{uri:result.video_url} }
videoWidth={1600}
videoHeight={900}
ignoreSilentSwitch={"ignore"}
style={styles.Vid}
onStart={oneVideoPlaying}
thumbnail={{uri:result.thumbnail_url}}
/>
</View>
<Card.Title title={result.title} subtitle={result.description} />
</Card>
</View>
);
}
const styles = StyleSheet.create ({
Container: {
flex: 1
},
CardView:{
marginLeft:10,
marginRight:10,
marginTop: 20,
borderRadius:25,
width:320,
height:300
},
VideoItem:{
},
Vid:{
borderTopLeftRadius:25,
borderTopRightRadius:25,
},
})
export default MediaItem;

URL not updated on NavigationStateChange react-native web-view

<WebView
ref={webView => { this.refWeb = webView; }}
style={{
height:height,
justifyContent: 'center',
alignItems: 'center',
}}
scrollEnabled={true}
//Loading URL
source={
{
uri: initialUrl,
}
}
//Enable Javascript support
javaScriptEnabled={true}
domStorageEnabled={true}}
injectedJavaScript={injectScript}
automaticallyAdjustContentInsets = {true}
startInLoadingState={true}
onNavigationStateChange={this.onNavigationStateChange}/>
onNavigationStateChange = (navState) => {
console.log(navState);
if(navState.url===initialUrl){
this.setState({
canGoBack: navState.canGoBack,
canGoForward: navState.canGoForward,
changedUrl:false
})
}else{
this.setState({
canGoBack: navState.canGoBack,
canGoForward: navState.canGoForward,
changedUrl : true
})
}
}
I am using react-native-webview but onNavigationStatechange not updating navState url it remains same when i click internal links inside webview.
Also got to know some issue due to Single Page Application, Can anyone suggest the approach to handle and overcome this problem.

React Native stored array data with asyncstorage returns nothing

I am trying to build a lunch picker app that allows user to add their own menu. I want to save user data into array by using AsyncStorage. However, my value returns nothing even though the array has values. Below is my code.
//Main screen
class HomeScreen extends React.Component {
//initial
constructor(props) {
super(props);
this.state = {
isReady: false,
myMenu: '????',
menutext: '',
randomArray: ['a', 'b', 'c'],
visibility: false,
};
}
_loadMenu = async () => {
try{
const loadMenu = await AsyncStorage.getItem("menuInStorage")
const parsedLoadMenu = JSON.parse(loadMenu)
const myReturn = [...this.state.randomArray, parsedLoadMenu]
this.setState({randomArray: myReturn})
}
catch(err){
alert(err)
}
}
//get input from textinput field and add to array
addMenu = newMenu => {
//...
this._saveMenu(this.state.randomArray)
};
_saveMenu = (saving) => {
const saveMenu = AsyncStorage.setItem("menuInStorage", JSON.stringify(saving))
}
//control modal
setModalVisibility(visible) {
this.setState({visibility: visible});
}
//UI
render() {
return (
<View style={styles.mainContainer}>
<View style={[styles.container, {flexDirection: 'row', justifyContent: 'center'}]}>
<TextInput
style={{ height: 40, fontSize: 20, paddingLeft: 15, textAlign: 'left', width: 250, borderBottomColor: '#D1D1D1', borderBottomWidth: 1 }}
placeholder=".."
onChangeText={menutext => this.setState({ menutext })}
value={this.state.menutext}
/>
<Button
title=".."
onPress={() => this.addMenu(this.state.menutext)}
buttonStyle={{width:100}}
backgroundColor="#2E282A"
/>
</View>
<Text>{'\n'}</Text>
<Button
onPress={() => this.setModalVisibility(true)}
title=".."
buttonStyle={{width: 150}}
backgroundColor="#2E282A"
/>
</View>
<Modal
onRequestClose={() => this.setState({ visibility: false })}
animationType={'slide'}
transparent={false}
visible={this.state.visibility}
>
<View style={[styles.modalContainer, {marginBottom: 100}]}>
<Text style={[styles.text, { fontWeight: 'bold', padding: 20, backgroundColor: '#9090DA', borderBottomColor: '#5C5C8B',
borderBottomWidth: 1,}]}>
{'<'}List will be here{'>'}
</Text>
<ScrollView style={{height: "94%"}}>
<View style={styles.row}>{this.state.randomArray}</View>
</ScrollView>
<Button
buttonStyle={{justifyContent: 'center', marginTop: 5}}
backgroundColor="#2E282A"
onPress={() => this.setModalVisibility(!this.state.visibility)}
title="Close"
/>
</View>
</Modal>
</View>
);
}
}
How the app supposed to work is, when user clicks a button, the modal shows all data in array called 'randomArray'. After user added their custom text, it should be added at the end of the randomArray. I want to save this data to the disk and load from the disk when the app is launched. At this moment, I can load array data, but it doesn't keep user data. My current code returns nothing. I need your help. Thanks.
It looks like the logic in _loadMenu is slightly incorrect on this line:
const myReturn = [...this.state.randomArray, parsedLoadMenu]
If I understand correctly, you're expecting parsedLoadMenu to be a value of type Array. The line above will basically append the value parsedLoadMenu to the resulting array stored in myReturn - in the case of your code, this will mean the last item of myReturn will be an array, which would be incorrect from what I see in your code. Consider updating this line as shown:
/*
Add ... before parsedLoadMenu to concatenate the two arrays in myReturn
*/
const myReturn = [...this.state.randomArray, ...parsedLoadMenu]
By adding the ... as shown, this causes the two arrays this.state.randomArray and parsedLoadMenu to be concatenated together in myReturn. It would also be worth checking the parse result from JSON.parse() to ensure that it is an array before attempting this concatenation:
_loadMenu = async () => {
try{
const loadMenu = await AsyncStorage.getItem("menuInStorage")
let parsedLoadMenu = JSON.parse(loadMenu)
/*
Consider an additional check here to ensure the loaded data is of
correct Array type before proceeding with concatenation
*/
if(!Array.isArray(parsedLoadMenu)) {
parsedLoadMenu = [];
}
/* Concatenate the two arrays and store result in component state */
const myReturn = [...this.state.randomArray, ...parsedLoadMenu]
this.setState({randomArray: myReturn})
}
catch(err){
alert(err)
}
}
Also, consider revising the addMenu logic, so that the entire array of menu items in your is persisted to AsyncStorage rather than the newly added menu item only, as you are currently doing:
addMenu = (newMenu) => {
/*
Persist current randomArray with newMenu item appended
*/
this._saveMenu([...this.state.randomArray, newMenu])
};
Hope this helps!