Prevent Facebook login from remounting the app - reactjs

There seems to be an issue with my implementation of the Facebook login for react-native
When I click on "Log in with the Facebook app" it opens the facebook app, then when I click "Continue" it reopens my app but it performs a remount. Part of my logic is to open another modal after that for some extra information from the user but calling this.setState() in the callback from the facebook login gives a `Can't call setState (or forceUpdate) on an unmounted component. And I can't seem to figure out how to get around this.
facebookLogin = async () => {
let result;
try {
this.setState({
loggingInWithFacebook: true
})
LoginManager.setLoginBehavior('native');
result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
} catch (nativeError) {
this.setState({
loggingInWithFacebook: false
})
this.props.dispatch(openSnackBar({
message: "There was an error opening facebook to login"
}))
}
if (result.isCancelled) {
this.setState({
loggingInWithFacebook: false
})
} else {
this.FBGraphRequest('first_name, last_name, id, email', this.FBLoginCallback);
}
}
FBGraphRequest = async (fields, callback) => {
const accessData = await AccessToken.getCurrentAccessToken();
const infoRequest = new GraphRequest('/me', {
accessToken: accessData.accessToken,
parameters: {
fields: {
string: fields
}
}
}, callback);
new GraphRequestManager().addRequest(infoRequest).start();
}
FBLoginCallback = async (error, result) => {
if (error) {
this.setState({
loggingInWithFacebook: false
});
} else {
const {id, picture, ...rest} = result
this._handleSocialLogin({...rest, facebook: id})
}
}
_handleSocialLogin = (data) => {
this.props.dispatch(socialLogin(data)).then(res => {
this.props.dispatch(getGroups()).then(res => {
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
AsyncStorage.getItem('onBoarded').then((res)=> {
if (res === 'true') {
this.props.navigation.navigate("Map")
} else {
this.props.navigation.navigate("OnBoarding")
}
})
}).catch(err => console.log(err.response.body))
}).catch(err => {
if (err.err === 'not found') {
this.setState({
enterPhoneModalVisible: true,
socialData: data
})
} else {
this.props.dispatch(
openSnackBar({
message: "There was an error logging in, please try again"
})
);
this.setState({
loggingInWithFacebook: false,
loggingInWithGoogle: false
})
}
})
}
EDIT:
My Appdelegate looks like this
#import <FBSDKCoreKit/FBSDKCoreKit.h>
In my didFinishLaunchingWithOptions
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
BOOL handledFB = [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
];
BOOL handledRCT = [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
return handledFB || handledRCT;
EDIT 2:
I seem to have gotten it to work by changing my openUrl to the following
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
] || [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
Any idea why this works?
EDIT 3:
I'm still getting a Login Failed error randomly and there doesn't seem to be any definitive way to cause this to happen it just happens sometimes and then trying to login again right after it usually goes through

Related

How to recover SIP js Invitation Object or Session Object in React Js on page refresh

I am implementing Audio/Video call with SIP js and Astrisk server in React JS.I was successful on creating the WebRTC Audio/Video calling. But I am facing an issue with storing the Invitation or Session Object for SIP js. Because Circular JSON data can't be stringed to store.
Assume someone has started calling and the other end got notification of calling and in that case if the page refreshed or reloaded I am unable to recover the call session to take any action(answer/ decline)
/**
* The following code is inside useState and the dependency are handled properly.
* For making it simple and sort I have just copied the required parts. */
const simpleUserDelegate = {
onCallAnswered: (session) => {
console.log(` Call answered`);
if (simpleUser) {
let remoteVideoTrack = simpleUser.getRemoteVideoTrack(session);
if (remoteVideoTrack) {
} else {
setIsAudioCall(true);
}
}
setIsCallAnswered(true);
setIsCallRecieved(false);
localStorage.setItem('isCallRecieved',null);
localStorage.setItem('callerName',null);
localStorage.setItem('callerImage',null);
setIsCallling(false);
},
onCallCreated: (session) => {
setCallSession(session);
console.log(session,` Call created`);
//console.log('session====>',JSON.stringify(session))
// localStorage.setItem('callerUserAgent',JSON.stringify(session._userAgent));
setIsCallling(true);
localStorage.getItem('callerUserAgent')
},
onCallReceived: (invitation) => {
console.log('invitation',invitation);
console.log('invitationSession',invitation.session);
setCallerActiveRoom(invitation._userAgent.options.displayRoomId);
setCallerName(invitation._userAgent.options.displayName);
setCallerImage(invitation._userAgent.options.displayImage);
localStorage.setItem('callerUserAgent',JSON.stringify(invitation.request));
console.log(` Call received`);
// dispatch(setActiveRoomId(invitation._userAgent.options.displayRoomId));
setIsCallRecieved(true);
localStorage.setItem('isCallRecieved',true);
localStorage.setItem('callerName',invitation._userAgent.options.displayName);
localStorage.setItem('callerImage',invitation._userAgent.options.displayImage);
},
onCallHangup: () => {
console.log(` Call hangup`);
setIsCallling(false);
setIsCallRecieved(false);
localStorage.setItem('isCallRecieved',null);
localStorage.setItem('callerName',null);
localStorage.setItem('callerImage',null);
setIsCallAnswered(false);
},
onCallHold: () => {
console.log(` Call hold`);
},
onRegistered: () => {
//console.log('session',session);
console.log(` Call registered`);
},
onUnregistered: () => {
console.log(` Call unregistered`);
},
onServerConnect: () => {
console.log(` server connect`);
},
onServerDisconnect: () => {
console.log(` server dis connect`);
}
};
let simpleUserOptions = {
// traceSip: false,
// logBuiltinEnabled: false,
delegate: simpleUserDelegate,
media: {
constraints: {
audio: true,
video: true
},
local: {
video: document.getElementById('localMedia')
},
remote: {
video: document.getElementById('remoteMedia'),
//audio: remoteAudioRef.current
}
},
userAgentOptions: {
logBuiltinEnabled: true,
logLevel: "debug",
authorizationPassword: password,
authorizationUsername: username,
uri: urI,
noAnswerTimeout : 30,
displayName: name,
displayImage: profileImage,
displayRoomId: `hi${displayRoomId}`
},
};
const simpleUserObj = new Web.SessionManager('wss://pbx.scinner.com:8089/ws', simpleUserOptions);
if(!simpleUserObj.isConnected()){
simpleUserObj
.connect()
.then(() => {
console.log(`${user.username} connected`);
simpleUserObj.register().then(() => {
console.log(`${user.username} registerd`);
}).catch((error) => {
alert("Failed to register.\n" + error);
});
})
.catch((error) => {
alert("Failed to connect.\n" + error);
});
setIsSARegistered(true);
setSimpleUser(simpleUserObj);
setCallerUserAgent
}else{
console.log('isconnected');
setIsSARegistered(true);
}
/**
Set calling
*/
const setCalling = (name, target) => {
simpleUser
.call(target, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: true
}
},
inviteWithoutSdp: false
}).then(() => {
console.log(`anon placed a call`);
}).catch((error) => {
console.error(`[${simpleUser.id}] failed to place call`);
console.error(error);
alert("Failed to place call.\n" + error);
});
//setIsCallling(true);
// console.log('isCallling', isCallling)
}
}
const answerCall = () => {
//callSession stored in local state
if (callSession) {
simpleUser.answer(callSession).then(() => {
console.log(`call answered`);
}).catch((error) => {
console.error(`call answered failed`);
console.error(error);
// alert("Failed to place call.\n" + error);
});
}
};

AsyncStorage doesn't save data but no error

Now I am aware that there are many of questions that asked the same thing. But I also found many that implemented the right methods but nothing worked for them even peoples' answers
Basically, I wanted to use AsyncStorage to save a few user preferences. At first everything worked and was saved correctly, but then suddenly nothing worked anymore.
I kept trying and trying, and made a very interesting finding.
First here's my code:
My import:
import AsyncStorage from '#react-native-async-storage/async-storage';
Default State:
state : AppState = {
messages: [],
isMuted: false
}
This is my getter. It works on init:
componentDidMount() {
this.getSettings();
}
async getSettings() {
try {
AsyncStorage.getItem("muted").then((muted)=> {
if (muted != null) {
this.setState({"isMuted": eval(muted)});
console.log("init! "+this.state.isMuted.toString());
} else {
console.log("init! found null");
}
})
} catch(e) {
// error reading value
}
}
Here's my setter, it works onPress of a button
onPressSpeaker = async () => {
var muted = !this.state.isMuted;
this.setState({"isMuted": muted});
try {
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! "+this.state.isMuted.toString());
const muted = await AsyncStorage.getItem('muted');
if(muted !== null) {
console.log("data found! "+this.state.isMuted.toString());
}
} catch (e) {
console.log("error")
}
};
I believe I set everything correctly.
But here's my log (from Flipper)
20:57:41.654
init! true
20:57:44.247
saved! false
20:57:44.256
data found! false
20:58:04.788
Running "Voice Message" with {"rootTag":51}
20:58:05.800
init! true
The last init was supposed to return the new value but it keeps returning the old value again and again, everytime I refresh (restart) the application.
Did I do something wrong? Am I missing something? Is there something I need to know about react-native-async-storage?
I think the problem that you are storing the this.state.isMuted value before the state mutates
To better understand you can try this code
onPressSpeaker = async () => {
var muted = !this.state.isMuted;
this.setState({"isMuted": muted});
try {
//Here we are trying to log the state before Add it to Storage
console.log('State => Before AsyncStorage.setItem', this.state.isMuted)
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! "+this.state.isMuted.toString());
const muted = await AsyncStorage.getItem('muted');
if(muted !== null) {
console.log("data found! "+this.state.isMuted.toString());
}
} catch (e) {
console.log("error")
}
};
Your log will now be like this
20:57:41.654
init! true
20:57:44.247
'State => Before AsyncStorage.setItem' true
20:57:44.247
saved! false
20:57:44.256
data found! false
Solution: So you need to write the function in the callback to the setState function
storeIsMuted = async () => {
try {
console.log("before setItem", this.state.isMuted.toString());
await AsyncStorage.setItem("muted", this.state.isMuted.toString());
console.log("saved! " + this.state.isMuted.toString());
//
const muted = await AsyncStorage.getItem("muted");
if (muted !== null) {
console.log("data found! " + this.state.isMuted.toString());
}
} catch (e) {
console.log("error");
}
};
onPressSpeaker = () => {
var muted = !this.state.isMuted
this.setState({ isMuted: muted }, async () => this.storeMuted());
};
Documentation
SetState

How to change "Request to join" button text to "Join Meeting" in iframe

I'm new to daily-co integration in react js. Can you please suggest how to change
"Request to Join" text to "Join Meeting". Thank in advance.
At present in Iframe all content is coming. Can any one please suggest how to change
the "Request to Join" text to "Join Meeting".
My Observations:
One api is calling at the time of page is loaded:
https://b.daily.co/call-ui/16c545a8520b661e39dc13c62b335ffea4cb3651/locales/en/translation.js
{ ....
"haircheck": {
....
"setup": {
"requestToJoin": "Request to join",
"title": "Are you ready to join?",
}
},
}
//React Class component:
import React from 'react';
import DailyIframe from '#daily-co/daily-js';
import Cookies from 'universal-cookie';
import axios from '../../util/axios';
import util from '../../util/util';
const cookies = new Cookies();
class VideoCallFrame2 extends React.Component {
constructor(props) {
super(props);
this.iframeRef = React.createRef();
this.state = {
authorizationToken: 'Bearer ------------',
roomName: '',
room: null,
token: null,
rooms: [],
roomUrlWithToken: null,
isVideoHidden: false,
joinedObject: null,
status: '',
askedQuestions: [],
};
}
componentDidMount() {
this.daily = DailyIframe.wrap(
this.iframeRef.current,
{
showLeaveButton: true,
});
this.setState({
...this.state,
roomUrlWithToken: this.props.meetingRoomUrl
});
this.startRoom();
let temp = this.daily.meetingState();
this.setState({ status: temp });
this.get_candidate_position();
}
get_candidate_position = (e) => {
this.setState({
positionDetails: response.data.candidate[0]
})
}
onHandleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
joinMeetingEvents = () => {
// Meeting related events
this.daily.on('loading', () => { console.log('Loading......') })
this.daily.on('loaded', () => { console.log('Loaded......') })
this.daily.on('joining-meeting', () => { console.log('Joining......') })
this.daily.on('joined-meeting', () => {
console.log('Joined......')
})
this.daily.on('app-message', (e) => {
console.log('app-messageapp-message app-message>>>>>>> ', e)
})
this.daily.on('left-meeting', (e) => {
console.log('Left Meeting......', e)
this.props.history.push('/thankyou')
})
this.daily.on('participant-joined', (e) => {
console.log('Partcipand Joined......', e);
this.setState({
...this.state,
isVideoHidden: true
})
if (this.state.joinedObject.user_id == '') {
}
})
this.daily.on('error', (e) => {
console.log('ERROR......', e)
})
}
leftMeeeting = () => {
this.daily.leave();
this.daily.destroy();
}
startRoom = async () => {
let res = await this.daily.join({
url: this.props.meetingRoomUrl
})
this.setState({
...this.state,
joinedObject: null
})
this.daily.on('loading', () => { console.log('Loading......') })
this.daily.on('loaded', () => { console.log('Loaded......') })
this.daily.on('joining-meeting', () => { console.log('joining-meeting......') })
this.daily.on('joined-meeting', () => {
console.log('Joined meeting......');
})
this.daily.on('joined-meeting', () => {
console.log('Joined meeting......');
})
this.daily.on('meeting-session-updated', () => {
console.log('meeting-session-updated......');
});
this.daily.on('access-state-updated', (evt) => {
console.log('access-state-updated......', evt);
if (evt.access.level == 'lobby') {
//Some code
}
});
this.daily.on('participant-joining', () => { console.log('participant-joining') })
this.daily.on('left-meeting', (e) => {
this.props.history.push('/thankyouPage');
});
this.daily.on("app-message", (e) => {
let Arr = this.state.askedQuestions;
if (
e &&
e.data &&
e.data.message &&
e.data.message.endInterview == "end") {
this.leftMeeeting();
}
});
this.daily.on('participant-joined', (e) => {
console.log('Partcipand Joined......', e);
setTimeout(() => {
this.daily.sendAppMessage({ message: { intervieweraskedQuestions: this.state.askedQuestions } }, '*');
}, 3000)
})
this.daily.on('error', (e) => {
console.log('ERROR......', e)
})
}
render() {
return (
<div className="" style={{ height: '450px' }}>
<iframe className="Video-Frame video-call-frame"
title="video call iframe"
ref={this.iframeRef}
allow="camera; microphone; fullscreen; display-capture"
></iframe>
</div>
)
}
}
export default VideoCallFrame2;
I work at Daily. :) "Request to join" is shown for private rooms with "knocking" enabled. We use this language because clicking the button will alert the room owner to let you in, so you do need to ask to join -- you can't just go straight in.
This can be turned off though. If you make the room public, it will say "Join meeting" instead of "Request to join" because anyone can join. Alternatively, you can make a meeting token for anyone trying to enter a private room so they don't need to ask to join. (In this case, the button text would also be updated to "Join meeting").
More generally, you can't update button text to something custom in the Daily Prebuilt UI, but you can build your on custom UI with our APIs. That's probably too much effort just to update one button, though. :)
I have not used daily.co before but I did a little digging and confirmed my suspicions: As far as I can tell, this is not possible.
In order to for a page to edit the contents of an iFrame, the frame must be on the same origin as its parent page, as per the Same Origin Policy.
Your page is on the origin http://localhost:3001, while the frame is on an origin owned by Daily, e.g. https://server.daily.co.
This policy exists for security reasons, an example is imagine some website https://attacker.com with a frame to https://bankaccount.com, without this policy the attacker could change a button on the frame from "Click to send all money to attacker" to "Click to receive your $1 million reward!"
The only method I have found that may be plausible after doing a couple searches for "origin", "host", etc. on docs.daily.co is this reference page for "Daily's Video Component System (VCS)", but from what I can tell this cannot solve the problem as this only allows you to add an overlay to the video call, not the frame itself.

scrollIntoView does not work on first load and after each refresh

I am seeing a very strange behavior in my chat. Once the chat is opened, the scroll moves only a tiny bit down only when there are images in the chat. When there is only text, it goes all the way down. Also, if I close the chat and open it again, the scroll goes all the way down regardless of the content. However, If I refresh the page, the scroll returns to its weird behavior. I am puzzled as to why this is happening. Here's my code:
Here's how the chat starts:
startChat () {
document.getElementById("myForm").style.display = "block";
const ref = firebase.firestore().collection('Chats').doc(this.state.uid).collection('Messages');
const query = ref.orderBy('timestamp', 'desc').limit(10)
this.unsubFromMessages = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
firebase.firestore().collection('Chats').doc(this.state.uid).
set({
name: this.state.displayName,
uid: this.state.uid,
email: this.state.email
}).then(console.log("info saved"))
.catch((error) => {
console.log("Error saving info to document: ", error);
});
}
snapshot.docChanges().reverse().forEach((change) => {
if (change.type === 'removed') {
console.log(change.doc.data().content)
}
else if (change.type === 'added') {
this.setState(state => {
const messages = [...state.messages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
else if (change.type === 'modified') {
const filteredMessages = this.state.messages.filter(message => message.body.allowed === "yes")
this.setState(state => {
const messages = [...filteredMessages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
});
}, (error) => {console.log(error)});
}
Here's the scroll function:
scrollToBottom = () => {
this.myRef.current.scrollIntoView({ behavior: "smooth" });
}
Here's the JSX of the chat:
<div className="form-popup" id="myForm">
<form className="form-container" onSubmit={this.chatFormSubmit}>
<h1>Chat</h1>
<label htmlFor="msg"><b>Message</b></label>
<div className="chatArea" id='messages'>
{
this.state.messages.map((message, index) => {
return message.body.uid === this.state.uid && !message.body.imageUrl
?
<p className="message-sent" key={index}>{message.body.content}</p>
:
message.body.uid === this.state.uid && message.body.imageUrl
?
<img src={message.body.imageUrl} className="message-sent" key={index}></img>
:
<p className="message-received" key={index}>{message.body.content}</p>
})
}
<div style={{ float:"left", clear: "both" }}
ref={this.myRef}>
</div>
</div>
And if the functions for closing and submitting messages to the chat are of any use, here they are:
closeForm() {
document.getElementById("myForm").style.display = "none";
this.setState({messages: []})
this.unsubFromMessages();
}
chatFormSubmit(e) {
e.preventDefault();
this.setState({ writeError: null });
firebase.firestore()
.collection('Chats')
.doc(this.state.uid)
.collection('Messages')
.doc()
.set({
docId: this.state.docId,
content: this.chatArea.current.value,
allowed: "yes",
timestamp: new Date(),
uid: this.state.uid,
name: this.state.displayName,
email: this.state.email
}, { merge: true })
.catch((error) => {
this.setState({ writeError: error.message });
})
.then(this.chatArea.current.value = '')
}
Again, I figured it out myself. Instead of calling "this.scrollToBottom()" in setTimeout, I should have simply passed it like this setTimeout( this.scrollToBottom, 2000). That is why setTimeout was not working and the scroll stopped half way. Credit goes to Felix Kling's comment in ReactJS: setTimeout() not working?.

unable to switch camera (front to rear) in react application using webrtc MediaDevices

This is the sample demonstration of what I'm intended to do.
If anyone has any idea about this fix to make it work or any new logic please do share.
This demonstration is implemented by using mediaStream API and
using react-webcam library which actually gives option to manage the camera view with the help of the props named videoConstraints={facingMode: 'user' or 'environment'} but it doesn't seems to be working.
when I click the camera switch ICON screen just hangs and nothing shows and also sometime it is working unexpectedly So ultimately I had to jumps to this native API solution which shows the code right below.
with all regards thanks in anticipation.
start() {
if (window.stream) {
console.log('found stream and clearing that', window.stream)
window.stream.getTracks().forEach(function(track) {
track.stop()
})
}
const constraints = {
video: true,
audio: false
}
return navigator.mediaDevices
.getUserMedia(constraints)
.then(this.gotStream)
.then(this.gotDevices)
.catch(this.handleError);
}
gotStream(stream) {
window.stream = stream // make stream available to console
// video.srcObject = stream;
// Refresh button list in case labels have become available
console.log('enumerating media devices ')
return navigator.mediaDevices.enumerateDevices()
}
gotDevices(mediaDevices) {
const { availableVideoInputs, videoConstraints } = this.state
mediaDevices.forEach(mediaDevice => {
// console.log(mediaDevice)
if (mediaDevice.kind === 'videoinput') {
console.log('found new video input ', mediaDevice)
availableVideoInputs.push({
deviceId: mediaDevice.deviceId,
label: mediaDevice.label
})
// availableVideoInputs.push('mediaDevice.deviceId.availableVideoInputs.push(mediaDevice.deviceId)')
}
})
console.log('aggregated availableVideoInputs new ', availableVideoInputs)
if (availableVideoInputs.length > 0) {
// there are accessible webcam
// setting first device as default to open
const tempVideoConstraint = {...videoConstraints}
if (availableVideoInputs[0].deviceId) {
console.log('availableVideoInputs[0] = ', availableVideoInputs[0])
tempVideoConstraint.deviceId = availableVideoInputs[0].deviceId
}
// console.log('putting tempVideoConstraint.facingMode ', tempVideoConstraint)
// if (availableVideoInputs[0].label.includes('back')) {
// tempVideoConstraint.facingMode = { exact: 'environment'}
// } else {
// // it is now turn to set front active
// tempVideoConstraint.facingMode = 'user'
// }
console.log('setting new video constrains ', tempVideoConstraint)
// this.setState({
// availableVideoInputs,
// // activeVideoInputID: availableVideoInputs[0].deviceId,
// // videoConstraints: tempVideoConstraint
// })
this.updateAvailableVideoStream(availableVideoInputs)
return Promise.resolve('done setting updateAvailableVideoStream')
} else {
// no webcam is available or accessible
console.error('ERR::VIDEO_STREAM_NOT_AVAILABLE')
}
}
updateAvailableVideoStream(availableVideoInputs) {
this.setState({ availableVideoInputs })
}
componentDidMount() {
this.start()
.then(data => {
console.log('data ', data)
console.log('update state ', this.state)
this.setState({
videoConstraints: {
...this.state.videoConstraints,
facingMode: 'user'
}
})
})
}
handleCameraSwitch() {
const { videoConstraints, availableVideoInputs, activeVideoInputID } = this.state
console.log('current video constraints ', videoConstraints)
const tempVideoConstraint = { ...videoConstraints }
// now check if it is possible to change camera view
// means check for another webcam
console.log({ availableVideoInputs })
console.log({ activeVideoInputID })
console.log({ remainingVideoStreams })
if (availableVideoInputs.length === 1) {
// cannot change the webcam as there is only 1 webcam available
console.error('ERR - cannot change camera view [Available Video Inputs: 1]')
return
}
// now change the view to another camera
// get the current active video input device id and filter then from available video stream
const remainingVideoStreams = availableVideoInputs.filter(videoStream => videoStream.deviceId !== activeVideoInputID)
// now check if in remainingVideoStreams there is more than 1 stream available to switch
// if available then show the Stream Selection List to user
// else change the stream to remainingVideoStreams[0]
console.log({ availableVideoInputs })
console.log({ activeVideoInputID })
console.log({ remainingVideoStreams })
if (remainingVideoStreams && remainingVideoStreams.length === 1) {
tempVideoConstraint.deviceId = remainingVideoStreams[0].deviceId
console.log('new video constraints ', {...tempVideoConstraint})
console.log('webcam ref ', this.webCamRef.current)
// if (remainingVideoStreams[0].label.includes('back') || tempVideoConstraint.facingMode === 'user') {
// tempVideoConstraint.facingMode = { exact: 'environment' }
// } else {
// // it is now turn to set front active
// tempVideoConstraint.facingMode = 'user'
// }
console.log('new video constraints with facing mode', tempVideoConstraint)
// const constraints = {
// video: tempVideoConstraint
// }
// navigator.mediaDevices.getUserMedia(constraints)
// .then((stream) => {
// console.log('stream -> ', stream)
// })
// .catch((error) => {
// console.error('Some error occured while changing the camera view ', error)
// console.log(error)
// })
this.setState({ videoConstraints: tempVideoConstraint, activeVideoInputID: remainingVideoStreams[0].deviceId })
} else {
// show the remaining stream list to user
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is the little variation of your Implementation.
But this will work exactly you've wished for.
Please see the below implementation for switching the camera front/back.
I have also added error validation like:
It will throw an error if there is no video stream available.
It will throw an error if there is only 1 video stream available when trying to access back camera.
Please do like and comment back if you have any other approach or want more clarification
componentDidMount() {
const gotDevices = (mediaDevices) =>
new Promise((resolve, reject) => {
const availableVideoInputs = []
mediaDevices.forEach(mediaDevice => {
if (mediaDevice.kind === 'videoinput') {
availableVideoInputs.push({
deviceId: mediaDevice.deviceId,
label: mediaDevice.label
})
}
})
if (availableVideoInputs.length > 0) {
resolve(availableVideoInputs)
} else {
reject(new Error('ERR::NO_MEDIA_TO_STREAM'))
}
})
navigator.mediaDevices.enumerateDevices().then(gotDevices)
.then((availableVideoInputs) => this.setState({ availableVideoInputs }))
.catch((err) => this.setState({ hasError: err }))
}
updateFileUploadView(newActiveView) {
this.setState({ activeFileUploadView: newActiveView })
const { hasError } = this.state
if (newActiveView === 'clickFromWebcam' && hasError) {
return console.error(hasError)
}
if (newActiveView === '') {
// means no view is active and clear the selected image
this.setState({ captureImageBase64: '', videoConstraints: defaultVideoConstraints })
}
}
changeCameraView() {
const { availableVideoInputs } = this.state
if (availableVideoInputs.length === 1) {
return console.error('ERR::AVAILABLE_MEDIA_STREAMS_IS_1')
}
this.setState({ resetCameraView: true })
setTimeout(() => {
const { videoConstraints: { facingMode } } = this.state
const newFacingMode = facingMode === 'user' ? { exact: 'environment' } : 'user'
this.setState({
videoConstraints: { facingMode: newFacingMode },
resetCameraView: false
})
}, 100)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
!resetCameraView ?
<Webcam
audio={false}
height='100%'
ref={this.webCamRef}
screenshotFormat="image/png"
minScreenshotWidth={screenShotWidth}
minScreenshotHeight={screenShotHeight}
screenshotQuality={1}
width='100%'
videoConstraints={videoConstraints}
/>
: 'Loading...'
As you can see this implementation is using react-webcam library
In componentDidMount you will first check for the available media stream of kind video input, then in other methods like changing cameraView i.e switching the camera to front/back.
I'm unmounting Webcam for 100ms only and then mounting it back with new videoConstraints either { facingMode: 'user' } or { facingMode: { exact: 'environment' } }
This approach will give your code a head start and you can play around the code and have fun.
Thank you!

Resources