我希望你做得很好。我在我的React Native应用程序中遇到了一个与相机集成相关的特殊问题。下面是我所面临的问题的详细解释:
问题描述:我正在使用React Native构建一个视频录制应用程序,用户可以在前置和后置摄像头之间切换。然而,我遇到了一个问题,在某些情况下摄像头无法正常显示在屏幕上。
(点击链接查看视频中的问题):https://streamable.com/gu67vj
以下是问题的具体内容:
1.摄像头关闭:打开应用时摄像头不显示,尤其是在初始加载时。只有当我强制重新渲染组件时(例如最小化应用并重新打开),摄像头才开始工作。
1.Key方法:我尝试使用key属性强制重新渲染摄像头组件,但没有完全解决问题。
附加信息:
*相机包:我使用react-native-vision-camera
进行相机集成。
*错误:已正确授予摄像头和麦克风权限并记录。
*测试:我尝试了不同的方法,包括使用key属性,但问题仍然存在。
*软件包版本:
{ "@react-native-camera-roll/camera-roll": "^6.0.0", "react": "18.2.0", "react-native": "0.72.6", "react-native-vector-icons": "^10.0.0", "react-native-video": "^5.2.1", "react-native-vision-camera": "^3.6.4" }
个
相关编码:
import React, { useState, useEffect, useRef } from 'react';
import {
StyleSheet,
StatusBar,
View,
Text,
TouchableOpacity,
ActivityIndicator,
SafeAreaView,
Alert,
ToastAndroid,
Linking,
} from 'react-native';
import {Camera, useCameraDevice} from 'react-native-vision-camera';
import Video from 'react-native-video';
import {CameraRoll} from '@react-native-camera-roll/camera-roll';
import Share from 'react-native-share';
import Icon from 'react-native-vector-icons/Ionicons';
function App() {
const [isLoading, setIsLoading] = useState(true);
const [isRecording, setIsRecording] = useState(false);
const [recordingDuration, setRecordingDuration] = useState(0);
const [isMuted, setIsMuted] = useState(false);
const [isFlashOn, setIsFlashOn] = useState(false);
const [isFrontCamera, setIsFrontCamera] = useState(true);
const [recordedVideo, setRecordedVideo] = useState();
StatusBar.setBackgroundColor('#3CF33A');
const device = useCameraDevice(isFrontCamera ? 'front' : 'back');
const camera = useRef(null);
useEffect(() => {
async function getPermissions() {
const cameraPermission = await Camera.requestCameraPermission();
const microphonePermission = await Camera.requestMicrophonePermission();
console.log(`Camera Permission Status: ${cameraPermission}`);
console.log(`Microphone Permission Status: ${microphonePermission}`);
if (cameraPermission === 'denied' || microphonePermission === 'denied') {
await Linking.openSettings();
}
setIsLoading(false);
}
getPermissions();
}, []);
useEffect(() => {
let interval;
if (isRecording) {
interval = setInterval(() => {
setRecordingDuration(prevDuration => prevDuration + 1);
}, 1000);
} else {
clearInterval(interval);
}
return () => {
clearInterval(interval);
};
}, [isRecording]);
if (isLoading) {
return (
<ActivityIndicator
size="large"
color="#3CF33A"
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}
/>
);
}
if (device == null)
return (
<ActivityIndicator
size="large"
color="#3CF33A"
style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}
/>
);
const toggleFlash = () => {
setIsFlashOn(prevState => !prevState);
};
const toggleCamera = () => {
setIsFrontCamera(prevState => !prevState);
};
const handleStartRecording = async () => {
try {
if (camera.current) {
setIsRecording(true);
ToastAndroid.show('Recording started', ToastAndroid.SHORT);
camera.current.startRecording({
flash: isFlashOn ? 'on' : 'off',
onRecordingFinished: video => {
setRecordedVideo(video);
console.log(video);
},
onRecordingError: error => console.error(error),
});
}
} catch (error) {
console.log(error);
}
};
const formatTime = seconds => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${String(minutes).padStart(2, '0')}:${String(
remainingSeconds,
).padStart(2, '0')}`;
};
const handleStopRecording = async () => {
try {
if (camera.current) {
setIsRecording(false);
setRecordingDuration(0);
ToastAndroid.show('Recording stopped', ToastAndroid.SHORT);
await camera.current.stopRecording();
}
} catch (error) {
console.log(error);
}
};
if (recordedVideo) {
const handleSaveVideo = async () => {
try {
if (recordedVideo && recordedVideo.path) {
await CameraRoll.save(recordedVideo.path, {
type: 'video',
album: 'Secrate Face Changer',
});
ToastAndroid.show('Video saved to gallery', ToastAndroid.SHORT);
} else {
ToastAndroid.show('No video to save', ToastAndroid.SHORT);
}
} catch (error) {
console.error(error);
ToastAndroid.show('Failed to save video', ToastAndroid.SHORT);
}
};
const handleShareVideo = async () => {
try {
if (recordedVideo && recordedVideo.path) {
const options = {
title: 'Share Video',
message: 'Check out this Video Recorded with Secret Face Changer!',
url: `file://${recordedVideo.path}`,
type: 'video/mp4',
};
await Share.open(options);
ToastAndroid.show('Video Shared', ToastAndroid.SHORT);
} else {
ToastAndroid.show('No video to share', ToastAndroid.SHORT);
}
} catch (error) {
console.log(error);
}
};
const handleDiscardVideo = () => {
Alert.alert(
'Discard Video',
'Are you sure you want to discard this video?',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Discard',
onPress: () => {
ToastAndroid.show('Video discarded', ToastAndroid.SHORT);
setRecordedVideo(null);
},
},
],
{cancelable: false},
);
};
const toggleMute = () => {
setIsMuted(prevState => !prevState);
};
return (
<SafeAreaView style={{flex: 1}}>
<Video
source={{uri: recordedVideo.path}}
ref={ref => {
this.player = ref;
}}
onBuffer={this.onBuffer}
onError={this.videoError}
repeat={true}
muted={isMuted}
controls={true}
resizeMode="cover"
style={StyleSheet.absoluteFill}
/>
<TouchableOpacity
onPress={handleDiscardVideo}
style={{top: 20, left: 10}}>
<Icon name="trash" size={30} color="white" />
</TouchableOpacity>
<TouchableOpacity
style={{position: 'absolute', top: 20, right: 20}}
onPress={toggleMute}>
<Icon
name={isMuted ? 'volume-mute' : 'volume-high'}
size={30}
color="white"
/>
</TouchableOpacity>
<TouchableOpacity
onPress={handleSaveVideo}
style={{position: 'absolute', top: 60, right: 20}}>
<Icon name="download" size={30} color="white" />
</TouchableOpacity>
<TouchableOpacity
onPress={handleShareVideo}
style={{position: 'absolute', top: 100, right: 20}}>
<Icon name="share-social" size={30} color="white" />
</TouchableOpacity>
</SafeAreaView>
);
}
return (
<View style={{flex: 1}}>
<Camera
style={StyleSheet.absoluteFill}
ref={camera}
device={device}
isActive={true}
video={true}
audio={true}
videoQuality="1080p"
/>
{isRecording ? (
<View style={styles.recordingTimer}>
<Text style={{color: 'white'}}>{formatTime(recordingDuration)}</Text>
</View>
) : null}
{!isFrontCamera && !isRecording ? (
<TouchableOpacity onPress={toggleFlash} style={{top: 20, left: 10}}>
<Icon
name={isFlashOn ? 'flash' : 'flash-off'}
size={34}
color="white"
/>
</TouchableOpacity>
) : null}
<TouchableOpacity onPress={toggleCamera} style={styles.cameraToggleBtn}>
<Icon
name={isFrontCamera ? 'camera-reverse' : 'camera'}
size={34}
color="white"
/>
</TouchableOpacity>
{!isRecording ? (
<TouchableOpacity
style={styles.recordBtn}
onPress={handleStartRecording}></TouchableOpacity>
) : (
<TouchableOpacity
style={styles.recordingBtn}
onPress={handleStopRecording}></TouchableOpacity>
)}
</View>
);
}
const styles = StyleSheet.create({
recordBtn: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: '#3cf33a',
position: 'absolute',
alignSelf: 'center',
bottom: 30,
elevation: 10,
},
recordingTimer: {
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'center',
width: 80,
height: 40,
fontSize: 15,
padding: 10,
backgroundColor: 'red',
elevation: 10,
position: 'absolute',
bottom: 100,
borderRadius: 25,
},
recordingBtn: {
width: 50,
height: 50,
borderRadius: 10,
backgroundColor: 'red',
position: 'absolute',
alignSelf: 'center',
bottom: 30,
},
cameraToggleBtn: {
position: 'absolute',
top: 20,
right: 20,
},
});
export default App;
字符串
2条答案
按热度按时间qgzx9mmu1#
在状态初始化期间使用useCameraDevices内的摄像机状态。
这段代码是为我工作,请尝试与这个例子
字符串
falq053o2#
作为
react-native-vision-camera
文档的例子,你应该在渲染之前检查相机是否可用:字符串