Current behavior / Bug
As soon as I pause/play the video on custom video control or slide on slider the video keep stutter to 1 or 2 seconds later sometim even 3-5 seconds
Video Clip Bug
https://drive.google.com/file/d/1C9Nj7FMcgA-I-I5Zha5jdJ-IBGXYGWJL/view?usp=sharing
Reproduction steps
click button to enter the new screen
video autoplay as soon the new screen load (if not doobleTap or singleTap on overlay, it will play as normal)
singleTap on video's overlay for the custom controls to appear then click pause button to pause video.
click play button to play video again, then video stutter.
Sometime slide on slider to seek new video's currentTime, video stutter (rarely not stutter)
If you pause the video the slide and play again and custom video control fadeout, video will play as normal not stutter
Expected behavior
pause / play video not stutter.
slide on progress video and play video again not stutter
Platform
Which player are you experiencing the problem on:
Android
React Native Setup
react-native : 0.64.2
gradle: 6.9
JDK: 11
Android Studio: Dolphin 2021.3.1
Android SDK API: 30
build tools: 30.0.2
#react-native-community/slider: 4.3.1
react-native-video: 5.2.0
react-native-orientation-locker: 1.5.0
react-native-vector-icons: 8.1.0
Remote Video URL
I called video URL from google cloud storage by using redux-saga.
I already called dispatch function to called api from parent screen. So, in this screen I only useSelector to get URL from redux-saga.
Can use any video URL with .mp4 file to substitute it.
Video sample
https://drive.google.com/uc?export=download&id=1C9Nj7FMcgA-I-I5Zha5jdJ-IBGXYGWJL
Sample Code
import React, { useRef, useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { View, StyleSheet, BackHandler, Dimensions, TouchableNativeFeedback, Text, StatusBar, Platform } from 'react-native';
import Video from 'react-native-video';
import Orientation from 'react-native-orientation-locker';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Slider from '#react-native-community/slider';
import { normalize } from 'react-native-elements';
const { width, height } = Dimensions.get("window");
Icon.loadFont();
let overlayTimer;
let Timer;
const VideoPlayerScreen = (props) => {
let lastTap = null;
const dispatch = useDispatch();
const { navigation } = props;
const [Fullscreen, setFullscreen] = useState(false);
const [paused, setpaused] = useState(false);
const [currentTime, setcurrentTime] = useState(0);
const [duration, setduration] = useState(0.1);
const [overlay, setoverlay] = useState(false);
const playerRef = useRef();
const contract = useSelector((state) => state.contract.contract);
useEffect(() => {
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
);
return () => backHandler.remove();
}, [])
const backAction = () => {
// navigation.goBack();
return true;
}
const videoUri = contract.videoURL
const FullscreenToggle = () => {
if (Fullscreen) {
Orientation.lockToPortrait();
StatusBar.setHidden(false)
navigation.setOptions({ headerShown: true });
setFullscreen(false)
} else {
Orientation.lockToLandscape();
StatusBar.setHidden(true)
navigation.setOptions({ headerShown: false });
setFullscreen(true);
}
}
const handleDoubleTap = (doubleTapCallback, singleTapCallback) => {
const now = Date.now();
const DOUBLE_PRESS_DELAY = 300;
if (lastTap && (now - lastTap) < DOUBLE_PRESS_DELAY) {
clearTimeout(Timer);
doubleTapCallback();
} else {
lastTap = now;
Timer = setTimeout(() => {
singleTapCallback();
}, DOUBLE_PRESS_DELAY);
}
}
const ShowHideOverlay = () => {
handleDoubleTap(() => {
}, () => {
setoverlay(true)
overlayTimer = setTimeout(() => setoverlay(false), 5000);
})
}
const backward = () => {
playerRef.current.seek(currentTime - 5);
clearTimeout(overlayTimer);
overlayTimer = setTimeout(() => setoverlay(false), 3000);
}
const forward = () => {
playerRef.current.seek(currentTime + 5);
clearTimeout(overlayTimer);
overlayTimer = setTimeout(() => setoverlay(false), 3000);
}
const onslide = (slide) => {
playerRef.current.seek(slide * duration);
clearTimeout(overlayTimer);
overlayTimer = setTimeout(() => setoverlay(false), 3000);
}
const getTime = (t) => {
const digit = n => n < 10 ? `0${n}` : `${n}`;
const sec = digit(Math.floor(t % 60));
const min = digit(Math.floor((t / 60) % 60));
const hr = digit(Math.floor((t / 3600) % 60));
// return hr + ':' + min + ':' + sec;
return min + ':' + sec;
}
const load = ({ duration }) => setduration(duration);
const progress = ({ currentTime }) => setcurrentTime(currentTime);
return (
<View style={styles.container}>
{Platform.OS === 'android' ?
< View style={Fullscreen ? styles.fullscreenVideo : styles.video}>
<Video
source={{ uri: videoUri }}
style={{ ...StyleSheet.absoluteFill }}
ref={playerRef}
paused={paused}
repeat={true}
onLoad={load}
onProgress={progress}
resizeMode={"contain"}
rate={1.0}
/>
<View style={styles.overlay}>
{overlay ?
<View style={{ ...styles.overlaySet, backgroundColor: '#0006', alignItems: 'center', justifyContent: 'space-around' }}>
<View style={{ width: 50, height: 50 }}>
<Icon name='replay-5' style={styles.icon} onPress={backward} />
</View>
<View style={{ width: 50, height: 50 }}>
<Icon name={paused ? 'play-arrow' : 'pause'} style={styles.icon} onPress={() => setpaused(!paused)} />
</View>
<View style={{ width: 50, height: 50 }}>
<Icon name='forward-5' style={styles.icon} onPress={forward} />
</View>
<View style={styles.sliderCont}>
<View style={{ ...styles.timer, alignItems: 'center' }}>
<View style={{ flexDirection: 'row' }}>
<Text style={{ color: 'white' }}>{getTime(currentTime)}/</Text>
<Text style={{ color: 'white' }}>{getTime(duration)}</Text>
</View>
<View style={{ margin: 5 }}>
<Icon onPress={FullscreenToggle}
name={Fullscreen ? 'fullscreen' : 'fullscreen-exit'}
style={{ fontSize: 20, color: 'white' }} />
</View>
</View>
<Slider
style={{ margin: 5 }}
maximumTrackTintColor='white'
minimumTrackTintColor='white'
thumbTintColor='white'
value={currentTime / duration}
onValueChange={onslide}
/>
</View>
</View>
:
<View style={styles.overlaySet}>
<TouchableNativeFeedback onPress={ShowHideOverlay}><View style={{ flex: 1 }} /></TouchableNativeFeedback>
</View>
}
</View>
</View>
:
<View style={styles.video}>
<Video
source={{ uri: videoUri }}
style={{ width: width, aspectRatio: width / (height - normalize(110)) }}
controls
// ref={(ref) => {
// this.player = ref;
// }}
/>
</View>
}
</View >
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'black'
},
// video: { width, height: width * .6, backgroundColor: 'black', justifyContent: 'center', alignItems: 'center' },
video: { width: "100%", aspectRatio: width / (height - normalize(80)), backgroundColor: 'black', alignItems: 'center', justifyContent: 'center' },
fullscreenVideo: {
width: "100%",
aspectRatio: 2 / 1,
backgroundColor: 'black',
...StyleSheet.absoluteFill,
elevation: 1
},
overlay: {
...StyleSheet.absoluteFillObject,
},
overlaySet: {
flex: 1,
flexDirection: 'row',
},
icon: {
color: 'white',
flex: 1,
textAlign: 'center',
textAlignVertical: 'center',
fontSize: 25
},
TextStyle: {
fontSize: 20, textAlign: 'center',
marginVertical: 100, color: '#6200ee', fontWeight: 'bold'
},
sliderCont: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0
},
timer: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 5
},
});
export default VideoPlayerScreen;
Related
On Android elements wrapped in BlurVIew don't positioning right.
It works on iOS devices fine. But on Android, elements wrapped in <BlurView>{children}</BlurView> run into each other. Screenshot is below
Sometimes when I change something in styles in dev mode, it works correctly, but when I build apk, bug appears again.
How can I fix it on Android?
Code
const BottomNavigationTabs: React.FC<BottomTabBarProps> = ({
descriptors,
state,
navigation,
}) => {
const focusedOptions = descriptors[state.routes[state.index].key].options;
return (
<>
<BlurView blurType="light"
blurAmount={15} style={{ height: 85, width: '100%', position: 'absolute', bottom: 0, }}>
<View style={{ display: 'flex', flexDirection: 'row', position: 'absolute', bottom: 0, justifyContent: 'space-between' }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const focusedColor = isFocused ? colors.text1 : colorsOld.black;
//Weird snippet, to render passed icon just call function with any return value, then just set color
const icon =
options.tabBarIcon &&
React.cloneElement(
//#ts-ignore
options.tabBarIcon((props: any) => props),
{ color: focusedColor }
);
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => {
navigation.emit({
type: "tabLongPress",
target: route.key,
});
};
const tabBtn = (
<TouchableOpacity
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
style={styles.tabButton}
onLongPress={onLongPress}
activeOpacity={0.7}
key={route.key}
>
<View style={styles.tabItem}>
<View style={styles.tabItemIcon}>{icon}</View>
<Text style={{ ...styles.tabItemLabel, color: focusedColor }}>
{label}
</Text>
</View>
</TouchableOpacity>
);
return tabBtn;
})}
</View>
</BlurView>
</>
);
};
const styles = StyleSheet.create({
container: {
alignItems: "center",
justifyContent: "center",
paddingTop: 9,
paddingBottom: 15,
},
tabButton: {
paddingTop: 8,
paddingBottom: 15,
paddingHorizontal: 10,
borderRadius: 10,
zIndex: 10000
},
tabsContainer: {
backgroundColor: colors.secondaryBg(0.5),
width: "100%",
left: 0,
bottom: 0,
zIndex: 999,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
},
tabItem: {
justifyContent: "center",
alignItems: "center",
},
tabItemIcon: {
marginBottom: 6,
},
tabItemLabel: {
fontSize: 12,
fontFamily: "Inter-Medium",
},
});
Found a solution here https://github.com/Kureev/react-native-blur/issues/483#issuecomment-1199210714.
Solution is to not set to <BlurView> position: absolute and to not wrapping anything with it. In my case looks like:
import React from "react";
import {
Text,
View,
ImageBackground,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { BlurView } from "#react-native-community/blur";
import { BottomTabBarProps } from "#react-navigation/bottom-tabs";
import colorsOld from "src/theme/colorsOld";
import colors from "src/theme/colors";
const BottomNavigationTabs: React.FC<BottomTabBarProps> = ({
descriptors,
state,
navigation,
}) => {
const focusedOptions = descriptors[state.routes[state.index].key].options;
return (
<View
style={{
display: "flex",
flexDirection: "row",
backgroundColor: colors.secondaryBg(0.5),
position: "absolute",
bottom: 0,
justifyContent: "space-between",
}}
>
<BlurView
blurType="light"
blurAmount={15}
style={{ height: 85, width: "100%", bottom: 0 }}
/>
<View style={styles.tabsContainer}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const focusedColor = isFocused ? colors.text1 : colorsOld.black;
//Weird snippet, to render passed icon just call function with any return value, then just set color
const icon =
options.tabBarIcon &&
React.cloneElement(
//#ts-ignore
options.tabBarIcon((props: any) => props),
{ color: focusedColor }
);
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => {
navigation.emit({
type: "tabLongPress",
target: route.key,
});
};
const tabBtn = (
<TouchableOpacity
accessibilityRole="button"
accessibilityState={isFocused ? { selected: true } : {}}
accessibilityLabel={options.tabBarAccessibilityLabel}
testID={options.tabBarTestID}
onPress={onPress}
style={styles.tabButton}
onLongPress={onLongPress}
activeOpacity={0.7}
key={route.key}
>
<View style={styles.tabItem}>
<View style={styles.tabItemIcon}>{icon}</View>
<Text style={{ ...styles.tabItemLabel, color: focusedColor }}>
{label}
</Text>
</View>
</TouchableOpacity>
);
return tabBtn;
})}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
alignItems: "center",
justifyContent: "center",
paddingTop: 9,
paddingBottom: 15,
},
tabButton: {
paddingTop: 8,
paddingBottom: 15,
paddingHorizontal: 10,
borderRadius: 10,
zIndex: 10000,
},
tabsContainer: {
width: "100%",
left: 0,
bottom: 0,
zIndex: 999,
position: 'absolute',
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
},
tabItem: {
justifyContent: "center",
alignItems: "center",
},
tabItemIcon: {
marginBottom: 6,
},
tabItemLabel: {
fontSize: 12,
fontFamily: "Inter-Medium",
},
});
export default BottomNavigationTabs;
I'm in a React Native project that the client wants the product image scrolls from top to bottom vice versa in a modal, how can I achive this?
I already know how to solve this...
I had to create a counter that increase or decrease Y axis of ScrollView every 0.5 seconds and checking if reached the top or bottom.
In the modal component file:
import React, { useState, useEffect } from 'react';
import { StyleSheet, Modal, ScrollView, View, Image, NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
import { Feather } from '#expo/vector-icons';
const ImageModal: React.FC<{ product: ProductType }> = ({ product }) => {
const [ axisY, setAxisY ] = useState<number>(0); // State that is used to know the current Y axis of ScrollView
const [ scrollToTop, setScrollToTop ] = useState<boolean>(false); // State that is used to checks if should go to top or bottom
// Handler that checks if ScrollView is scrolling to top or bottom
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
// HELP: https://newbedev.com/detect-scrollview-has-reached-the-end
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent; // Get scroll event properties
// If scrolling to top
if (scrollToTop) {
const isNearTop = contentOffset.y != 0; // Checks if Y axis reached the top of ScrollView
setScrollToTop(isNearTop); // Change the state value to FALSE, making ScrollView goes to bottom
} else {
const isNearBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height; // Checks if Y axis reached the bottom of ScrollView
setScrollToTop(isNearBottom); // Change the state value to TRUE, making ScrollView goes to top
}
}
// Increase or decrease current Y axis every 0.5 seconds
useEffect(() => {
const timer = setInterval(() => {
setAxisY(prev => !scrollToTop ? prev + 1.5 : prev - 1.5);
}, 50);
return () => clearInterval(timer);
}, [scrollToTop]);
return (
<Modal
visible={ true }
transparent={ true }
statusBarTranslucent={ true }
animationType="fade"
>
<View style={ styles.container }>
<View style={ styles.box }>
<ScrollView
overScrollMode="never"
style={ styles.scroll }
scrollEnabled={ false }
showsVerticalScrollIndicator={ false }
contentOffset={{ x: 0, y: axisY }}
onScroll={ handleScroll }
>
<View style={ styles.imageBox }>
<Image source={{ uri: product.image_url }} style={ styles.image } />
</View>
</ScrollView>
<View>
<Text>Some random text!</Text>
</View>
</View>
<TouchableOpacity style={ styles.closeButton } onPress={ onClose }>
<Feather name="x" size={ 30 } color="#fff" />
</TouchableOpacity>
</View>
</Modal>
);
}
// Main Styles
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
padding: 20
},
closeButton: {
width: 60,
height: 60,
borderWidth: 2,
borderRadius: 12,
marginLeft: 20,
borderColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#58585a'
},
box: {
backgroundColor: '#fff',
width: '80%',
borderRadius: 10,
flexDirection: 'column'
},
scroll: {
width: '100%',
height: '80%',
borderBottomWidth: 1,
borderColor: '#58585a'
},
imageBox: {
width: '100%',
height: 600,
},
image: {
width: '100%',
height: '100%',
resizeMode: 'cover',
borderTopLeftRadius: 10,
borderTopRightRadius: 10
}
});
export default ImageModal;
I'm using an expo-camera for video recording in my react native app. Everything is working great in development but when I generate an APK file so the camera is not recording the video.
I have used the useRef hook is my code which triggers when the start recording button is clicked and fires the startRecord() function which uses
const recordedVideo = await cameraRef.current.recordAsync();.
What i think that maybe cameraRef.current.recordAsync() is not working in the production.
Any Suggestions or solution would be very helpful.
Thanks!
**Video Recording Component**
import React, { useState, useEffect, useRef } from "react";
import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
// packages
import { Camera } from "expo-camera";
import * as FileSystem from "expo-file-system";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
} from "react-native-responsive-screen";
// Functional Component
const Recordvideo = (props) => {
// Ref
const cameraRef = useRef(null);
// States
const [video, setVideo] = useState(null);
const [recording, setRecording] = useState(false);
const [hasPermission, setHasPermission] = useState(null);
// getting camera permission
useEffect(() => {
getPermission();
}, [getPermission]);
const getPermission = async () => {
const { status } = await Camera.requestPermissionsAsync();
if (status === "granted") {
setHasPermission(true);
}
};
// start/stop video recording
const toogleRecord = () => {
if (recording) {
stopRecord();
} else {
startRecord();
}
};
// start recording
const startRecord = async () => {
if (cameraRef.current) {
setRecording(true);
const recordedVideo = await cameraRef.current.recordAsync();
setVideo(recordedVideo);
}
};
// stop recording
const stopRecord = async () => {
setRecording(false);
cameraRef.current.stopRecording();
};
// saving recorded video
const saveVideo = async () => {
let fileInfo = await FileSystem.getInfoAsync(video.uri);
if (fileInfo.size <= 5242880) {
props.navigation.push("Post", {
VIDEOURL: video.uri,
VIDEOID: 1,
});
} else {
Alert.alert("Video too large please upload a shorter video");
props.navigation.goBack();
}
};
// checking camera permission
if (hasPermission === false) {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>No access to camera</Text>
</View>
);
}
// return statement
return (
<View style={styles.responsiveBox}>
<Camera
ref={cameraRef}
style={{
justifyContent: "flex-end",
alignItems: "center",
flex: 1,
width: "100%",
}}
>
{video && (
<TouchableOpacity
onPress={saveVideo}
style={{
padding: 20,
width: "100%",
backgroundColor: "#fff",
}}
>
<Text style={{ textAlign: "center", color: "#000" }}>Complete</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={toogleRecord}
style={{
padding: 20,
width: "100%",
backgroundColor: recording ? "#ef4f84" : "#4fef97",
}}
>
<Text style={{ textAlign: "center" }}>
{recording ? "Stop" : "Record"}
</Text>
</TouchableOpacity>
</Camera>
</View>
);
};
// styles
const styles = StyleSheet.create({
responsiveBox: {
width: wp("100%"),
height: hp("100%"),
alignItems: "center",
justifyContent: "center",
},
});
export default Recordvideo;
Instead of taking useRef take it as state for example:
const [cameraRef , SetCameraRef] = useState()
And call ref inside Camera component like <Camera ref = {setCameraRef(ref)} ..../>
Actual Behaviour :
I am supposed to implement signature pad in landscape-right mode along with a timestamp of signature drawn. Then take a screenshot of the view, and save it in document directory (iOS) or external directory (Android) in portrait mode by rotating it. I was successful in implementing signature screen in landscape-right mode using transform: [{rotate: '90deg"}] css property, and react-native-signature-capture, save the captured screenshot of signature along with the timestamp of signature drawn in local directory using react-native-view-shot and convert it into base64 format using react-native-fs.
But the saved screenshot is not in portrait mode and I'm trying to rotate the image while saving it in document directory (iOS) or external directory (Android) without using any modules. I also tried rotating the image while saving it using canvas context API but could not find way to access canvas in react-native to rotate image while saving it as canvas is HTML DOM related.
Expected Behaviour :
I'm supposed to save the signature drawn along with timestamp in document directory (iOS) or external directory (Android) in portrait mode as shown in below screenshot.
Additional Resources :
Code :
render() {
return (
<View
style={{
flex: 1,
flexDirection: 'row',
overflow: "hidden",
}}>
<StatusBar hidden={true} />
<View
style={{
flex: 0.8,
flexDirection: 'row-reverse',
marginVertical: width / 18,
overflow: "hidden",
}}>
<ViewShot
ref="viewShot"
style={[styles.viewShot, { transform: [{ rotate: this.state.bool && '90deg' }] }]}>
{/* options={{ width: height, height: width }}> */}
<SignatureCapture
style={styles.signature}
ref={sign => (this.signComponent = sign)}
onSaveEvent={this._onSaveEvent}
onDragEvent={this._onDragEvent}
saveImageFileInExtStorage={true}
showNativeButtons={false}
showTitleLabel={false}
showBorder={false}
viewMode={'portrait'}
square={true}
backgroundColor={"white"}
maxSize={800}
rotateClockwise={!!true}
/>
<View
ref="timeRef"
style={{
width: width / 10,
height: width / 3,
justifyContent: 'flex-end',
flexDirection: 'row-reverse',
}}>
<View
style={{
width: width / 1.8,
height: width / 1.8,
transform: [{ rotate: '-90deg' }],
overflow: "hidden",
paddingLeft: width / 18,
paddingTop: width / 25
}}>
<Text style={styles.time}>{this.state.data}</Text>
</View>
</View>
</ViewShot>
<Image
ref="imageRef"
source={{ uri: this.state.imageUri }}
style={{ transform: [{ rotate: '90deg' }] }}
/>
</View>
<View
style={{
flex: 0.2,
alignItems: 'center',
justifyContent: 'space-around',
flexDirection: 'column',
overflow: "hidden",
backgroundColor: Colors.white,
}}>
<View
style={{
backgroundColor: Colors.darkGreen,
width: width / 2,
justifyContent: 'center',
alignItems: 'center',
paddingRight: width / 25,
paddingVertical: width / 37.5,
transform: [{ rotate: '-90deg' }],
overflow: "hidden",
}}>
<TouchableOpacity
hitSlop={{ top: 30, left: 50, right: 50, bottom: 30 }}
onPress={() => {
this.saveSign();
}}>
<Text style={{ fontSize: width / 18, color: Colors.white }}>Submit </Text>
</TouchableOpacity>
</View>
<View
style={{
backgroundColor: '#5476ab',
width: width / 2,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: width / 37.5,
transform: [{ rotate: '-90deg' }],
overflow: "hidden",
}}>
<TouchableOpacity
hitSlop={{ top: 30, left: 50, right: 50, bottom: 30 }}
onPress={() => {
this.resetSign();
}}>
<Text style={{ fontSize: width / 18, color: Colors.white }}>Clear</Text>
</TouchableOpacity>
</View>
<View
style={{
backgroundColor: '#73c5de',
width: width / 2,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10,
transform: [{ rotate: '-90deg' }],
}}>
<TouchableOpacity
hitSlop={{ top: 30, left: 50, right: 50, bottom: 30 }}
onPress={() => {
this.onCancel();
}}>
<Text style={{ fontSize: width / 18, color: Colors.white }}>Cancel</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}
_onSaveEvent(result) {
this.setState({ signature: result.pathName,
markResult: result.encoded });
}
_onDragEvent() {
this.setState({ dragged: true });
}
saveSign() {
if (this.state.dragged === true) {
this.setState({ bool: true });
this.refs.viewShot.capture().then(uri => {
this.setState({ imageUri: uri });
console.log("uri123", uri);
RNFS.readFile(this.state.imageUri,
'base64').then(image => {
console.log("image123", image);
this.setState({ sign: image }, () => {
this.ChangeOrientation();
});
});
});
} else {
Alert.alert('NALG', 'Please sign the signature
pad to submit');
}
ChangeOrientation() {
this.props.getSignature(this.state.sign);
this.props.setModalVisible(!this.props.modalVisible);
}
Screenshot of Actual Behaviour :
Screenshot of Expected Behaviour :
Environment:
react-native : 0.61.1
react-native-view-shot : ^3.0.2
react-native-signature-capture : ^0.4.10
react-native-fs : ^2.16.2
const showImagePicker = () => {
const options = {
title: i18n.t('issueReport.form.photoSelection'),
storageOptions: {
skipBackup: true,
path: 'images',
},
allowsEditing: true,
};
ImagePicker.showImagePicker(options, response => {
const { originalRotation } = response
console.log('Response = ', response);
console.log('originalRotation = ', originalRotation);
if (originalRotation === 90) {
setOriginalRotation(90)
} else if (originalRotation === 270) {
setOriginalRotation(-90)
}
setSelectedImageFile(response);
if (response && response.height === 3000) {
setLandscape(true)
}
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else if (response.customButton) {
console.log('User tapped custom button: ', response.customButton);
} else {
const source = { uri: response.uri };
console.log('Image Path', source);
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
setSelectedImage(source);
setPhotoSelectVisible(true);
}
});
};
For more detail
https://github.com/react-native-image-picker/react-native-image-picker/issues/655
I am creating an application for android and ios in react-native(0.57.7) and using react-native-video to play videos uploaded into vimeo. After integrate react video plugin, tested in both device. In ios it works perfectly but in android, I am not able to play video in full-screen mode. Here is my code for Android:
import React, { PureComponent } from 'react';
import {
View,
Text,
Image,
ImageBackground,
StyleSheet,
SafeAreaView,
TouchableOpacity,
ActivityIndicator
} from 'react-native';
import PropTypes from 'prop-types'
import Video from "react-native-video";
import Orientation from 'react-native-orientation-locker';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from '../components/Resposive';
import RF from "react-native-responsive-fontsize"
export default class Videoplayer extends PureComponent {
constructor(props){
super(props);
this.state = {
loading: true,
videoFileArr:[],
showPlayer:false,
playing:false
}
}
componentDidMount() {
Orientation.lockToLandscape();
this.fetchVimeoVideo();
}
componentWillUnmount() {
Orientation.lockToPortrait();
}
goToHome(){
Orientation.lockToPortrait();
this.props.navigation.goBack();
}
fetchVimeoVideo = async () => {
await this.setState({ loading: true });
const { navigation } = this.props;
const jsonReceived = navigation.getParam('item', {})
const url = "https://api.vimeo.com/me/videos/" + jsonReceived.video_link
console.log(url)
const response = await fetch(
url, {
method: "get",
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization:"Bearer f9e937d64037e657addcf088f28e6cb5"
}
});
const jsonResponse = await response.json();
const { files} = jsonResponse;
if (response.status != 200) {
alert(response.status)
}
console.log(files)
await this.setState({ videoFileArr:files, loading: false });
};
renderOptions = () => {
if (this.state.loading === true) {
return (
<View style={{
flex: 1,
alignItems: "center",
justifyContent: "center"
}}>
<ActivityIndicator size="large" color="#00ff00" />
<Text style={{ fontFamily: "Futura Std", fontSize: RF(3.0), fontWeight: "900", color: "#244E25", textAlign: "center" }}>Please wait while we are loading questions for you</Text>
</View>
)
}else if (this.state.videoFileArr.length> 0 && this.state.playing === false) {
const { navigation } = this.props;
const jsonReceived = navigation.getParam('item', {})
return(
<ImageBackground style={{width:"100%",height:"100%",alignItems:"center",justifyContent:"center"}} source={{uri:jsonReceived.video_banner}}>
<TouchableOpacity
onPress={() => {
this.setState({playing:true})
}}
>
<Image source={require("../assets/Common/Play/playIcon.png")}/>
</TouchableOpacity>
</ImageBackground>
)
} else if (this.state.videoFileArr.length > 0 && this.state.playing === true) {
return (
<View style={styles.container}>
<Video source={{ uri:this.state.videoFileArr[0].link}} // Can be a URL or a local file.
ref={ ref =>
this.player = ref
} // Store reference
onBuffer={this.onBuffer} // Callback when remote video is buffering
onError={this.videoError} // Callback when video cannot be loaded
style={styles.backgroundVideo}
controls={true}
paused={false}
fullscreen={true}
/>
</View>
)
}
}
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, overflow: "hidden" }}>
<View style={{ flex: 1, backgroundColor: "green" }}>
{this.renderOptions()}
</View>
{/* top navigationBar */}
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
width: "100%",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
height: 80,
backgroundColor: null
}}
>
<TouchableOpacity onPress={
()=>this.goToHome()
}>
<Image style={{ margin: 8 }} source={require("../assets/Common/goBack/goBack.png")} />
</TouchableOpacity>
<TouchableOpacity>
<Image style={{ margin: 8 }} source={require("../assets/Common/Star/starOff.png")} />
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container:{ flex: 1, justifyContent: "center"},
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
});
and this is the output screen where I can not play video in full screen:
Please help, What I'm doing wrong ?
I have solved fullscreen of video frame just adding resizeMode on Video component:
<Video source={{ uri:this.state.videoFileArr[0].link}} // Can be a URL or a local file.
ref={ ref =>
this.player = ref
} // Store reference
onBuffer={this.onBuffer} // Callback when remote video is buffering
onError={this.videoError} // Callback when video cannot be loaded
style={styles.backgroundVideo}
controls={true}
paused={false}
fullscreen={true}
resizeMode="cover"
/>
I was struggling with the same problem couple of days ago but I made it work for me in android. I hope this will help you also.
You will need to install some other package as well that is
1. react-native-video-controls
2. react-native-orientation
now In your Screen where the video will be played.
import React, { Component } from 'react'
import {
Text,
StyleSheet,
StatusBar,
Dimensions,
Alert,
Modal,
BackHandler,
TouchableOpacity,
ToastAndroid,
} from 'react-native'
import { Container, View, Button, Icon, List, ListItem } from 'native-base';
import Video from 'react-native-video';
import Orientation from 'react-native-orientation';
const sample = require('../assets/demo.mp4');
export default class Player extends Component {
constructor(props) {
super(props);
this.onLoad = this.onLoad.bind(this);
this.onProgress = this.onProgress.bind(this);
}
state = {
rate: 1,
volume: 1,
muted: false,
resizeMode: 'contain',
duration: 0.0,
currentTime: 0.0,
active: false,
modalVisible: false,
fullScreen: true,
};
onLoad(data) {
this.setState({ duration: data.duration });
}
onProgress(data) {
this.setState({ currentTime: data.currentTime });
}
getCurrentTimePercentage() {
if (this.state.currentTime > 0) {
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
} else {
return 0;
}
}
renderRateControl(rate) {
const isSelected = (this.state.rate == rate);
return (
<ListItem>
<TouchableOpacity onPress={() => { this.setState({ rate: rate }) }}>
<Text style={{ fontWeight: isSelected ? "bold" : "normal" }}>
{rate}x
</Text>
</TouchableOpacity>
</ListItem>
)
}
renderResizeModeControl(resizeMode) {
const isSelected = (this.state.resizeMode == resizeMode);
return (
<TouchableOpacity onPress={() => { this.setState({ resizeMode: resizeMode })
}}>
<Text style={[styles.controlOption, { fontWeight: isSelected ? "bold" :
"normal" }]}>
{resizeMode}
</Text>
</TouchableOpacity>
)
}
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
fullScreen = () => {
Orientation.getOrientation((err, orientation) => {
if (orientation == 'LANDSCAPE') {
Orientation.lockToPortrait();
} else {
Orientation.lockToLandscape();
}
});
}
backAction = () => {
Orientation.getOrientation((err, orientation) => {
if (orientation == 'LANDSCAPE') {
Orientation.lockToPortrait();
}
});
};
componentDidMount() {
this.backHandler = BackHandler.addEventListener(
"hardwareBackPress",
this.backAction
);
}
componentWillUnmount() {
this.backHandler.remove();
}
render() {
const { modalVisible, paused } = this.state
const url = `https://www.sample-
videos.com/video/mp4/720/big_buck_bunny_720p_10mb.mp4`;
const flexCompleted = this.getCurrentTimePercentage() * 100;
const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
return (
<View style={styles.container}>
<StatusBar hidden={true} />
<Video source={sample}
style={styles.fullScreen}
rate={this.state.rate}
paused={this.state.paused}
volume={this.state.volume}
muted={this.state.muted}
resizeMode={this.state.resizeMode}
onLoad={this.onLoad}
onProgress={this.onProgress}
onEnd={() => { alert('Done!') }}
controls
repeat={true} />
<View style={[{ left: 0 }, styles.rateControl]}>
<Button
transparent
onPress={() => {
this.fullScreen();
}}
>
<Icon type="FontAwesome5" name="compress" style={{ color: "#fff",
fontSize: 15 }} />
</Button>
</View>
<View style={styles.rateControl}>
<Button
transparent
onPress={() => {
this.setModalVisible(true);
}}
>
<Icon type="FontAwesome5" name="ellipsis-v" style={{ color:
"#fff", fontSize: 15 }} />
</Button>
</View>
{/* <View style={styles.controls}>
<View style={styles.generalControls}>
<View style={styles.resizeModeControl}>
{this.renderResizeModeControl('cover')}
{this.renderResizeModeControl('contain')}
{this.renderResizeModeControl('stretch')}
</View>
</View>
<View style={styles.trackingControls}>
<View style={styles.progress}>
<View style={[styles.innerProgressCompleted, { flex: flexCompleted }]} />
<View style={[styles.innerProgressRemaining, { flex: flexRemaining }]} />
</View>
</View>
</View> */}
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<View style={styles.closeModal}>
<Button
transparent
onPress={() => { this.setModalVisible(!modalVisible);
}}
>
<Icon name="close" />
</Button>
</View>
<View>
<Text style={{ textAlign: 'center', fontWeight: 'bold'
}}>Play Rate</Text>
<List style={{ flexDirection: 'row', justifyContent:
'space-between', alignItems: 'center' }}>
{this.renderRateControl(0.25)}
{this.renderRateControl(0.5)}
{this.renderRateControl(1.0)}
{this.renderRateControl(1.5)}
{this.renderRateControl(2.0)}
</List>
</View>
</View>
</View>
</Modal>
</View >
)
}
}
const styles = StyleSheet.create({
backgroundVideo: {
// position: 'absolute',
// top: 0,
// left: 0,
// bottom: 0,
// right: 0,
width: Dimensions.get('window').width,
height: Dimensions.get('window').width * .6,
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
fullScreen: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
controls: {
backgroundColor: "transparent",
borderRadius: 5,
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
},
progress: {
flex: 1,
flexDirection: 'row',
borderRadius: 3,
overflow: 'hidden',
},
innerProgressCompleted: {
height: 20,
backgroundColor: '#cccccc',
},
innerProgressRemaining: {
height: 20,
backgroundColor: '#2C2C2C',
},
generalControls: {
flex: 1,
// flexDirection: 'row',
borderRadius: 4,
overflow: 'hidden',
paddingBottom: 10,
},
rateControl: {
flexDirection: 'row',
position: 'absolute',
top: 10,
right: 10
},
volumeControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
resizeModeControl: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
controlOption: {
alignSelf: 'center',
fontSize: 11,
color: "white",
paddingLeft: 2,
paddingRight: 2,
lineHeight: 12,
},
centeredView: {
flex: 1,
marginTop: '22%'
},
modalView: {
width: '100%',
padding: '5%',
backgroundColor: "white",
position: 'absolute',
bottom: 10,
},
openButton: {
backgroundColor: "#F194FF",
borderRadius: 20,
padding: 10,
elevation: 2
},
closeModal: {
alignItems: 'flex-end',
margin: -10
},
textStyle: {
color: "white",
fontWeight: "bold",
textAlign: "center"
},
modalText: {
marginBottom: 15,
textAlign: "center"
}
});
I hope this will help you.
Most of suggestion involves adding additional libraries to achieve fullscreen in react-native-vide for android. Its not really needed.
What we need is set the height and width of to full screen to achieve this. we need to use variable instead of fixed value then setState to refresh the view.
Below is the sample typescript code that works for react-native-video fullscreen:
import React, {Component} from 'react';
import {
View,
StyleSheet,
TouchableWithoutFeedback,
Dimensions,
} from 'react-native';
import Video from 'react-native-video';
import Icon from 'react-native-vector-icons/FontAwesome';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
interface Props {}
interface States {
paused: boolean;
inFullScreen: boolean;
}
export default class Sandbox extends Component<Props, States> {
player: any;
videoStyle: {minWidth: number; minHeight: number};
constructor(props: Props) {
super(props);
this.state = {
paused: true,
inFullScreen: false,
};
this.videoStyle = {minWidth: 400, minHeight: 400};
}
render() {
return (
<View style={{height: 400, width: 400}}>
<Video
source={{
uri:
'https://www.radiantmediaplayer.com/media/big-buck-bunny-360p.mp4',
}} // Can be a URL or a local file.
ref={(ref: any) => {
this.player = ref;
}} // Store reference
controls={false}
paused={this.state.paused}
resizeMode={'cover'}
style={this.videoStyle}
/>
<View style={styles.controls}>
<TouchableWithoutFeedback
onPress={() => {
this.setState({paused: !this.state.paused});
}}>
<Icon name={'play'} size={30} color="#FFF" />
</TouchableWithoutFeedback>
<TouchableWithoutFeedback
onPress={() => {
if (!this.state.inFullScreen) {
//Orientation.lockToLandscape();
this.videoStyle = {
minHeight: Dimensions.get('window').height,
minWidth: Dimensions.get('window').width,
};
} else {
this.videoStyle = {
minHeight: 400,
minWidth: 400,
};
}
this.setState({inFullScreen: !this.state.inFullScreen});
}}>
<MaterialIcon name={'fullscreen'} size={30} color="#FFF" />
</TouchableWithoutFeedback>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
controls: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
height: 48,
top: 20,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 10,
},
});
As #Selva answered we can use variables for video sizes i.e width & height and make it occupy full screen and use a stack to place the video in a stack screen above the current screen and pop it when needed. Even if it's in a flat list.
Had same issue fix it by removing the wrapping and setting just the height to the player, no other library needed and no need to manage the orientation.
<Video source={{uri: this.props.videoData.uri}}
onEnd={this.onVideoEnd}
style={styles.player}
controls={true} />
<View
onStartShouldSetResponder={() => setPaused(!paused)}
style={{
marginHorizontal: 10,
backgroundColor: "black",
position: "relative",
transform: [{ rotate: "90deg" }],
// justifyContent:'center',
// alignItems:'center'
}}
>
<Video
onEnd={onEnd}
onLoad={onLoad}
onLoadStart={onLoadStart}
posterResizeMode={"cover"}
onProgress={onProgress}
paused={paused}
ref={(ref) => (videoPlayer.current = ref)}
resizeMode={'stretch'}
source={{
uri: "url",
}}
style={{
...styles.backgroundVideo,
height: width,
aspectRatio:2
// width: "100%"
// alignSelf:'center'
// transform: [{ rotate: '90deg'}]
}}
/>
just give it a width of
width:'100%'