Customize X control on react-native-video - reactjs

Is there any possibility to customise the event on X for react-native-video?
To summarise my problem i have an app when on press play it will trigger a modal with a fullscreen video and starts to play automatically, this is the behavior i was seeking.
Video example
But then my issue is when i close the video it looks like is still playing on background. I tried to pass a onClose prop to close the modal but still facing the same issue.
This is what i tried:
Parent component:
<Modal
style={{justifyContent: 'center', alignItems: 'center'}}
animationType="slide"
transparent={false}
visible={modalVisible}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}>
{modalVisible && <View
style={{
backgroundColor: colors.background,
justifyContent: 'center',
alignItems: 'center',
}}>
<VideoPlayer onClose={() => setModalVisible(!modalVisible)}/>
</View>}
</Modal>
Video player:
import React, {
Component
} from 'react';
import {
StyleSheet,
Dimensions,
} from 'react-native';
import Video from 'react-native-video';
// Dimensions
const {width, height} = Dimensions.get('screen');
class VideoPlayer extends Component {
constructor(props) {
super(props);
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
this.state = {
orientation: isPortrait() ? 'portrait' : 'landscape'
}
// Event Listener for orientation changes
Dimensions.addEventListener('change', () => {
this.setState({
orientation: isPortrait() ? 'portrait' : 'landscape'
});
});
}
render() {
return (
<Video
fullscreen={true}
onFullscreenPlayerWillDismiss={() => this.props.onClose()}
onEnd={() => this.props.onClose()}
playWhenInactive={false}
playInBackground={false}
ref={p => { this.videoPlayer = p; }}
source={{uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4'}}
style={{width: this.state.orientation === 'landscape' ? height : width, height: this.state.orientation === 'landscape' ? width : height }}
controls={true}
fullscreenOrientation="all"
/>
)
}
}
const styles = StyleSheet.create({
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
});
export default VideoPlayer;
If anyone faced similar issue please let me know.
Thanks in advance.

Solved by using the package react-native-video-controls and customise the onBack prop:
<VideoPlayer
onBack={() => this.props.onClose()}
fullscreen={true}
onExitFullscreen={() => this.props.onClose()}
onEnd={() => this.props.onClose()}
playWhenInactive={false}
playInBackground={false}
source={{uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4'}}
fullscreenOrientation="all"
/>

Related

How to use a Pan responder to scroll a scrollView (React)

I have a pan responder overlaying the whole screen and a scrollview underneath it.
I would like to call the scrollTo() method and using Pan responders X/Y travel positions to scroll.
I am using this Pan Responder example code to create a Y value that can increment or decrement as you swipe up or down on the screen.
https://reactnative.dev/docs/panresponder
I don't know how to get myScroll view to listen to this Y value change.
Any help is appreciated. thanks
// You should be able to run this by copying & pasting
import { createRef, useRef } from "react";
import { Animated, PanResponder, StyleSheet, Text, View } from "react-native";
// some refs...
const scrollRef = createRef();
const buttonRef = createRef();
// panResponder Hook
const useScreenPanResponder = () => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
y: pan.y._value,
});
},
onPanResponderMove: Animated.event([null, { dy: pan.y }]),
onPanResponderRelease: () => {
pan.flattenOffset();
},
})
).current;
return { pan, panResponder };
};
// custom button
const ButtonWrapper = ({ children }) => {
return (
<View
onTouchStart={() => {
buttonRef.current = Date.now();
}}
onTouchEnd={() => {
if (Date.now() - buttonRef.current < 500) {
console.log("Actual Press");
} else {
console.log("Not a Press");
}
}}
>
{children}
</View>
);
};
// long list of text
const Content = () => {
const data = Array.from(Array(100));
return (
<View style={{ backgroundColor: "orange", flexDirection: "column" }}>
{data.map((i) => (
<Text style={{ fontSize: 17, color: "black" }}>Some Content</Text>
))}
</View>
);
};
export default function App() {
const { pan, panResponder } = useScreenPanResponder();
console.log("pan!", pan);
console.log("scrollRef", scrollRef);
const scrollToPosition = () => {
// scrollRef.current.scrollTo({ y: pan.y });
};
return (
<View style={styles.container}>
{/* Container taking up the whole screen, lets us swipe to change pan responder y pos */}
<Animated.View
style={{
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "rgba(0,255,0,.5)",
}}
{...panResponder.panHandlers}
/>
{/* A animated container that reacts to pan Responder Y pos */}
<Animated.View
style={{
transform: [{ translateY: pan.y }],
}}
>
<ButtonWrapper>
<View style={styles.box} {...panResponder.panHandlers} />
</ButtonWrapper>
</Animated.View>
{/* Here we need the scrollView to be controlled by the Pan Y pos */}
<Animated.ScrollView ref={scrollRef}>
<Content />
</Animated.ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
titleText: {
fontSize: 14,
lineHeight: 24,
fontWeight: "bold",
},
box: {
height: 150,
width: 150,
backgroundColor: "blue",
borderRadius: 5,
},
});

React Native transition maxHeight

How can I make a transition with maxHeight in React Native?
The equivalent code in React would be
function App() {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="App">
<div className={`collapsible ${isOpen ? 'opened' : ''}`}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Close' : 'Open'}
</button>
</div>
);
}
And the css
.collapsible {
max-height: 0;
transition: max-height 0.35s ease-out;
overflow: hidden;
}
.opened {
max-height: 200px;
}
Here is a working codesandbox
How can I make the same but in React Native?
My guess is that you want to persist the animation when "opening" the view.
This is not supported out of the box using a StyleSheet object in React Native. You will have to implement this yourself using the Animated API.
https://facebook.github.io/react-native/docs/animations
Example:
import React from "react"
import { Animated } from "react-native"
import { Text, StyleSheet, TextProps } from "react-native"
import { TouchableOpacity } from "react-native-gesture-handler"
class AnimatedComponent {
constructor(props){
super(props)
this.state = { open: false }
this.animatedHeightValue = new Animated.Value(0)
}
_triggerAnimation = () => {
Animated.timing(this.animatedHeightValue, {
toValue: this.state.open ? 0 : 200,
duration: 200
}).start(() => {
this.setState({open: !this.state.open})
})
}
render() {
return(
<View>
<Animated.View style={{height: this.animatedHeightValue, backgroundColor: "blue"}}>
<Text>
{"Hello world"}
</Text>
</Animated.View>
<TouchableOpacity onPress={this._triggerAnimation}>
<Text>
{"Press for magic"}
</Text>
</TouchableOpacity>
</View>
)
}
Indentation is awful sorry, but should give an idea.
Hmmm.. I'm not completely sure did you actually meant this. But in case we're on the same page, check my Snack at: https://snack.expo.dev/#zvona/maxheight-animation
Here is the actual code (in case link of the snack gets deprecated):
import React, { useState, useRef } from 'react';
import {
Animated,
Text,
TouchableOpacity,
View,
StyleSheet,
} from 'react-native';
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [contentStyle, setContentStyle] = useState({ visibility: 'hidden' });
const currentHeight = useRef(new Animated.Value(0)).current;
const [maxHeight, setMaxHeight] = useState(0);
const toggleContent = () => {
setIsOpen(!isOpen);
Animated.timing(currentHeight, {
toValue: isOpen ? 0 : maxHeight,
duration: 350,
}).start();
};
const getContentHeight = ({ nativeEvent }) => {
if (maxHeight !== 0) {
return;
}
const { height } = nativeEvent.layout;
setMaxHeight(height);
setContentStyle({visibility: 'visible', height: currentHeight });
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={toggleContent} style={styles.button}>
<Text>{'Open'}</Text>
</TouchableOpacity>
<Animated.View
style={[styles.content, contentStyle]}
onLayout={getContentHeight}>
<Text style={styles.paragraph}>
Change code in the editor and watch it change on your phone! Save to
get a shareable url.
</Text>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
backgroundColor: '#ffffff',
padding: 8,
},
button: {
justifyContent: 'center',
alignItems: 'center',
width: 120,
borderWidth: 1,
borderColor: 'black',
backgroundColor: '#c0c0c8',
padding: 10,
},
content: {
borderWidth: 1,
borderColor: 'blue',
overflow: 'hidden',
},
paragraph: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
export default App;
The key ingredient of detecting the maxHeight is in onLayout event of the Animated.View. Then some magic on displaying content through state handling.
And if you want to define maxHeight manually, then just rip the getContentHeight off.

How do I transition from one image to another by changing state in React-Native

I have two images with little difference between them and each corresponding to a particular state. I need to smoothly transition from one to the other when I change the state so that the effect feels like only the part which is different in the two images has undergone animation,the rest of the image staying as it is.
I want it to work so that when I render the second image on stateChange, only the rod like part in the second image appears to fade in,the rest remaining still.
I think this can be achieved without using any animation libraries like react-transition-group probably by using some of the life cycle methods in React and obviously, the AnimatedAPI. The major issue that I am facing is that when I update the state I have no control over the previous image that was rendered. I somehow want the previously rendered image to stay until newly rendered Component appears and does its animation. Here is what I tried to do. I have this ImageLoader Component which renders an image while providing a fading-in animation to it.
class ImageLoader extends React.Component {
constructor(){
super()
this.opacity= new Animated.Value(0)
}
componentDidUpdate(){
{this.onLoad()}
}
onLoad = () => {
this.opacity.setValue(0);
Animated.timing(this.opacity, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}
render() {
return (
<Animated.Image onLoad={this.onLoad}{...this.props}style={[
{opacity: this.opacity,}, this.props.style,
]}
/>
);
}
}
export default class App extends React.Component {
state={
no:1,
}
render() {
let Dun=()=>{return this.state.no==1?
<ImageLoader source={require('./assets/img1.PNG')}/>: <ImageLoader
source={require('./assets/img2.PNG')}/>
}
const calc=()=>{
this.setState((state)=>({no:Math.abs(state.no-1)}));
}
return (
<View style={styles.container}>
<View style={{height:100,marginLeft:50}}>
{Dun()}
<Button onPress={()=>{calc()}}> Press</Button>
</View>
</View>
);
}
}
You could use 2 animated images to give the impression that one is fading into the other. Here is a solution based on your example:
import React from 'react';
import { Animated, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import images from 'src/images';
const styles = StyleSheet.create({
image: {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0
}
});
class ImageSwitcher extends React.Component {
fadeInOpacity = new Animated.Value(0);
fadeOutOpacity = new Animated.Value(1);
state = {
prevSource: null
};
componentDidMount() {
this.onLoad();
}
componentDidUpdate() {
this.onLoad();
}
componentWillReceiveProps({ source: newSource }) {
const { source } = this.props;
if (newSource !== source) {
this.setState({ prevSource: source });
}
}
onLoad = () => {
this.fadeInOpacity.setValue(0);
this.fadeOutOpacity.setValue(1);
Animated.timing(this.fadeInOpacity, {
toValue: 1,
duration: 500,
useNativeDriver: true
}).start();
Animated.timing(this.fadeOutOpacity, {
toValue: 0,
duration: 500,
useNativeDriver: true
}).start();
};
render() {
const { prevSource } = this.state;
return (
<View
style={{
width: 200,
height: 200
}}
>
<Animated.Image {...this.props} style={[styles.image, { opacity: this.fadeInOpacity }]} resizeMode="cover" />
{prevSource && (
<Animated.Image {...this.props} style={[styles.image, { opacity: this.fadeOutOpacity }]} resizeMode="cover" source={prevSource} />
)}
</View>
);
}
}
export default class App extends React.Component {
state = {
source: images.first
};
handleToggle = () => this.setState(({ source }) => ({ source: source === images.first ? images.second : images.first }));
render() {
const { source } = this.state;
return (
<View style={{ flex: 1 }}>
<ImageSwitcher source={source} />
<TouchableOpacity onPress={this.handleToggle}>
<Text>Toggle Image</Text>
</TouchableOpacity>
</View>
);
}
}
const SwitchImage = (props) => {
const [previousImage, setPreviousImage] = useState('');
const opacity = useRef(new Animated.Value(0)).current;
useEffect(()=>{
Animated.timing(opacity, {
toValue: 1,
duration: 300,
easing: Easing.spring,
useNativeDriver: true
}).start(()=>{
setPreviousImage(props.source);
opacity.setValue(0);
})
}, [props.source])
return(
<View style={{width: props.style.width, height: props.style.height}}>
<FastImage
resizeMode={props.resizeMode}
source={previousImage}
style={{width: props.style.width, height: props.style.height, position: 'absolute'}}
/>
<Animated.View style={{opacity: opacity}}>
<FastImage
resizeMode={props.resizeMode}
source={props.source}
style={{width: props.style.width, height: props.style.height, position: 'absolute'}}
/>
</Animated.View>
</View>
)}

how to hide and show loading spinner - Activity Indicator react native, managing props and state

I have created a custom Activity Indicator class and I want to control the hide/show of it from where I use it.
Here is what I have done.
CustomActivityIndicator.js
import React, { Component } from 'react';
import { ActivityIndicator, View, Text, TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
import colors from '../../../styles/colors';
import { consoleLog } from '../../../utils/globalFunctions';
const { width, height } = Dimensions.get('window');
export default class CustomActivityIndicator extends Component {
constructor(props){
super(props);
this.state = {
show: this.props.show
}
}
static getDerivedStateFromProps(nextProps, prevState) {
let outObj = {};
consoleLog('Login - nextProps.show - ' + nextProps.show + ' prevState.show - ' + prevState.show);
if(nextProps.show !== prevState.show) {
return {
show: nextProps.show
};
}
}
render() {
consoleLog('CustomActivityIndicator - ' + this.state.show );
return (
<View style={styles.container}>
<ActivityIndicator
animating = {this.state.show}
color = {colors.primaryColor}
size = "large"/>
</View>
);
}
}
const styles = StyleSheet.create ({
container: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center'
}
})
I am using in Login this is just to demonstrate
I am initially setting the show state to false in Login and When I click the Login button I want to show the ActivityIndicator.
Can you guide me on where I am getting it wrong.
Login.js
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
}
loginEndpointDecider = () => {
this.setState({show: true}) ;
}
render() {
return (
<ScrollView style={styles.mainContainer}>
<CustomActivityIndicator show={this.state.show}/>
<TouchableOpacity title='Transactions'
style = {{ height: 60, backgroundColor: '#673fb4', marginTop: 20, alignItems: 'center', justifyContent: 'center' }}
onPress={() => {
this.loginEndpointDecider();
}}>
<CommonText style={{ color: 'white'}}>
{strings.signInLower}
</CommonText>
</TouchableOpacity>
</ScrollView>
);
}
}
Thanks
R
Instead of having all the props inside the actual component itself - a better way in the "React mindset" is to be able to conditionally render a flexible component. What this means is that inside your Login.js file you can use the state to display something inside the render method.
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
show: false
};
}
loginEndpointDecider = () => {
this.setState({show: true}) ;
}
render() {
return (
<ScrollView style={styles.mainContainer}>
{this.state.show ? <CustomActivityIndicator/> : "not shown"}
<TouchableOpacity title='Transactions'
style = {{ height: 60, backgroundColor: '#673fb4', marginTop: 20, alignItems: 'center', justifyContent: 'center' }}
onPress={() => {
this.loginEndpointDecider();
}}>
<CommonText style={{ color: 'white'}}>
{strings.signInLower}
</CommonText>
</TouchableOpacity>
</ScrollView>
);
}
}
The {this.state.show ? <CustomActivityIndicator/> : "not shown"} is shorthand for an if statement.
How about wrapping the ActivityIndicator with curly braces and the state value of show like so:
{this.state.show && <CustomActivityIndicator />}
I don't think you'd really need the show prop in that case.

Expo Camera only opening once with React Navigation

I set the Expo camera to open on the middle tab with react navigation. However, the camera only opens when I click on that tab the first time. If I switch off of it and go back it's just a black screen. Also the take a picture button is not there. (I am new with react native and kinda coding as a whole)
'use strict';
import React, { Component } from 'react';
import { createBottomTabNavigator } from 'react-navigation';
import { Camera, Permissions } from 'expo';
import {
AppRegistry,
Dimensions,
StyleSheet,
Text,
TouchableOpacity,
View,
Button
} from 'react-native';
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
</View>
);
}
}
class CameraView extends React.Component {
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
};
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
<TouchableOpacity
style={{
flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back,
});
}}>
<Text
style={{ fontSize: 18, marginBottom: 10, color: 'white' }}>
{' '}Flip{' '}
</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
}
}
class SettingsScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
}
}
export default createBottomTabNavigator({
Home: HomeScreen,
Camera:CameraView,
Settings: SettingsScreen
});
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
top: 250
},
capture: {
flex: 0,
backgroundColor: '#fff',
borderRadius: 5,
padding: 15,
paddingHorizontal: 20,
alignSelf: 'center',
margin: 20
}
});
With react navigation 5.x
import { useIsFocused } from '#react-navigation/native';
export const CameraView = (props) => {
const isFocused = useIsFocused();
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
useIsFocused Documentation
I had some issue.
This code solved it:
import { useIsFocused } from '#react-navigation/native';
function MyComponent() {
const isFocused = useIsFocused()
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
Old navigation
import { withNavigationFocus } from 'react-navigation'
render() {
const { isFocused } = this.props
return (
<View>
{ isFocused && <RNCamera ... /> }
</View>
)
}
export default withNavigationFocus(Camera)
To make this work you need to:
1.
import { NavigationEvents } from 'react-navigation';
state = { loaded: true }
render() {
const { loaded } = this.state;
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={payload => this.setState({loaded: true})}
onDidBlur={payload => this.setState({loaded: false})}/>
<View style={styles.cameraArea}>
{loaded && (
<Camera
type={Camera.Constants.Type.back}
ref={ref => {
this.camera = ref;
}}
/>
)}
</View>
The idea is to hide this camera view (onDidBlur-> loaded: false), then when you come back (onWillFocus is triggered and change loaded to true). When render() function is called it will show the <Camera /> again.
If you have any questions, feel free to ask.
This works for me. ( Navigation 5.x )
if you are using a different screen for CAMERA, you can easy unmount the screen when moving to another.
Reason of this behavior : Only one Camera preview can be active at any
given time. If you have multiple screens in your app, you should
unmount Camera components whenever a screen is unfocused.
<Stack.Screen name="camera" component={CameraScreen} options={{unmountOnBlur: true}}/>
</Stack.Navigator>
Documentation Link : https://docs.expo.io/versions/latest/sdk/camera/
Thanks.
I got it working by using NavigationEvents to determine if the tab is in focus and from there mount and unmount the camera. This also frees up the camera, if you need to use it in another screen. Here is what I would do in your example:
import { NavigationEvents } from 'react-navigation';
...
class CameraView extends React.Component {
constructor(props) {
super(props)
this.state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
isFocused:true
};
}
...
render(){
//...your existing if's
} if(this.state.isFocused === false){
return (
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
)
}
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
//...the rest of your camera code
}
I hope it works for you as well
I solved it using the hook useIsFocused from react-navigation/native. I tested it on Android, iOS and Web
import { useIsFocused } from '#react-navigation/native';
...
const isFocused = useIsFocused();
...
return (
isFocused && (
<Camera
ref={(ref) => {
camera = ref;
}}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
)
);
If you are using RN Expo with React Navigation - Tab Navigator.
Then just use unmountOnBlur - unmountOnBlur Documentation
This will force the camera to unmount on every navigation focus changes.
in Expo react native
import { useIsFocused } from '#react-navigation/native';
const isFocused = useIsFocused();
{isFocused &&
<Camera
type={type}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{ flex: 1 }}
flashMode={flash}
zoom={zoom}
/>
}

Resources