react-native-webrtc Mic not closing after video call on iOS - ios

Our iOS app has audio video calling implemented using the following technologies:
"react-native": "0.63.4"
"react": "16.13.1"
"react-native-webrtc": "^1.87.3"
"react-native-incall-manager": "^3.3.0"
iOS version 14.4.1
Our calling module works like the following:
First request and initiate audio call
Then request and initiate video call
On the code side things work like this:
We call the getStream() function which gets the user media for audio call i.e Audio only
Then we call the startStream() function which connects the peer connection
On requesting video we call the getVideoStream() method to get Audio and Video streams
Call startStream() again to start peer connection with video
The scenario is as follows:
We start off by connecting an audio call. On success the audio call is connected and works fine as expected
We request for video and connect video call, all works fine as expected and I receive video on both ends
When I disconnect the call and stop tracks using this.state.localStream.getTracks(), the mic does not close. An orange indicator for mic is visible on iOS.
Important Notes:
Disconnecting from the audio call closes the mic just fine
Even if we get video stream on audio call and disconnect without connecting video it still works fine and closes both tracks
Its only when I connect the video is when the issue arises
Calling InCallManager.stop() closes the mic but does not open it on second call. The mic does not open on second call and the orange mic indicator on iOS is not shown.
Get User Media Audio Call
getStream() {
InCallManager.setSpeakerphoneOn(false);
InCallManager.setMicrophoneMute(false);
mediaDevices.enumerateDevices().then((sourceInfos) => {
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (
sourceInfo.kind === 'videoinput' &&
sourceInfo.facing === (true ? 'front' : 'back')
) {
videoSourceId = sourceInfo.deviceId;
}
}
mediaDevices
.getUserMedia({
audio: true,
})
.then((stream) => {
this.setState({
localStream: stream,
});
})
.catch((error) => {
// Log error
console.log('stream get error', error);
});
});
}
Get User Media for Video Call
getVideoStream() {
this.state.peerConn.removeStream(this.state.localStream);
InCallManager.setSpeakerphoneOn(false);
InCallManager.setMicrophoneMute(false);
mediaDevices.enumerateDevices().then((sourceInfos) => {
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (
sourceInfo.kind === 'videoinput' &&
sourceInfo.facing === (true ? 'front' : 'back')
) {
videoSourceId = sourceInfo.deviceId;
}
}
mediaDevices
.getUserMedia({
audio: true,
mirror: true,
video: {
mandatory: {
minWidth: 500,
minHeight: 300,
minFrameRate: 30,
},
facingMode: true ? 'user' : 'environment',
optional: videoSourceId ? [{sourceId: videoSourceId}] : [],
},
})
.then((stream) => {
this.setState(
{
localStream: stream,
},
() => {
this.startStream();
},
);
})
.catch((error) => {
// Log error
console.log('stream get error', error);
});
});
}
Start Stream Function
startStream() {
console.log('start Stream');
this.newPeerConnection();
setTimeout(() => {
this.state.peerConn
.createOffer()
.then((sessionDescription) =>
this.setLocalAndSendMessage(sessionDescription),
)
.catch((error) => this.defaultErrorCallback(error));
}, 3000);
}
newPeerConnection()
newPeerConnection() {
var peerConn = new RTCPeerConnection({
iceServers: turnServer,
});
peerConn.onicecandidate = (evt) => {
console.log(`OnIceCan`);
if (evt.candidate) {
this.state.connection.invoke(
'addIceCandidate',
parseInt(this.state.ticket.pkTicketId),
JSON.stringify({
type: 'candidate',
sdpMLineIndex: evt.candidate.sdpMLineIndex,
sdpMid: evt.candidate.sdpMid,
candidate: evt.candidate.candidate,
}),
);
}
};
peerConn.addStream(this.state.localStream);
peerConn.addEventListener(
'addstream',
(stream) => {
InCallManager.setForceSpeakerphoneOn(false);
this.setState({
isSpeakerEnabled: false,
});
this.setState({
remoteStream: stream,
showAudioCallTimer: true,
});
},
false,
);
this.setState(
{
peerConn,
});
}
Close Tracks
if (this.state.localStream) {
const tracks = this.state.localStream.getTracks();
tracks.map((track, index) => {
track.stop();
});
}
if (this.state.peerConn) {
this.state.peerConn.removeStream(this.state.localStream);
this.state.peerConn.close();
if (!this.state.callRatingSubmitted && this.state.remoteStream) {
this._handleCallFeedbackModal(true);
}
}

Related

Camera video not showing on iOS

I'm attempting to access the camera of a current user device and display the camera's video before they can take a picture. It works perfectly on Android and browsers, but not on iPhones for some strange reason. Even when the screen is black, I can still capture a picture. It's just a dark video camera.
Snippet of the code
useEffect(() => {
navigator.mediaDevices
.enumerateDevices()
.then(function (devices) {
devices.forEach(function (device) {
if (device.kind == "videoinput") {
let temp = videoInputs;
temp.push(
device.kind + ": " + device.label + " id = " + device.deviceId
);
setVideoInputs(temp);
}
});
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
});
}, []);
const getVideo = () => {
navigator.mediaDevices
.getUserMedia({
video: {
// width: 1920,
// height: 1080,
facingMode: orientation ? "environment" : "user",
},
})
.then((stream) => {
console.log(stream);
setError("");
setVideoStream(stream);
let video = videoRef.current;
video.srcObject = stream;
video.play();
})
.catch((err) => {
setError(err.toString())
// setError("Permission denied. Turn on your camera to continue");
console.error(err);
});
};
useEffect(() => {
getVideo();
return () => {
stopVideo();
};
}, [orientation]);

Expo Notification Error in ios [Error: Another async call to this method is in progress. Await the first Promise.]

Please provide the following:
SDK Version: "^42.0.0",
Platforms(Android/iOS/web/all): iOS
Add the appropriate "Tag" based on what Expo library you have a question on.
Hello,
[expo-notifications] Error encountered while updating server registration with latest device push token., [Error: Another async call to this method is in progress. Await the first Promise.]
This error often occurs in expo app. And always occurs in build ios app.
Using the following code:
async function registerForPushNotificationsAsync() {
let token
if (Constants.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
allowAnnouncements: true,
},
})
finalStatus = status
}
if (finalStatus !== 'granted') {
console.log('noti cancel')
return
}
// token = (await Notifications.getExpoPushTokenAsync()).data
try {
await Notifications.getExpoPushTokenAsync().then((res) => {
console.log('getExpoPUshTokenAsync >> ', res)
token = res.data
})
} catch (err) {
console.log('getExpoPushTokenAsync error >> ', err)
}
} else {
Alert.alert(
'Must use physical device for Push Notifications'
)
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
})
}
return token
}
I have received a new credential, but I am still getting the error.

WebRTC - how to switch between getUserMedia and getDisplayMedia tracks inside RTCPeerConnection

I'm trying to develop an app where users can can video call to each other and share their screens using WebRTC technology. I have succeed with either video call or screen sharing app and now I'm trying to make it to be able to switch between getUserMedia and getDisplayMedia on button click during a call inside the same RTCPeerConnection but it doesn't work.
This is how I thought it could work:
function onLogin(success) {
var configuration = { offerToReceiveAudio: true, offerToReceiveVideo: true, "iceServers" : [ { "url" : "stun:stun.1.google.com:19302" } ] };
myConnection = window.RTCPeerConnection ? new RTCPeerConnection(configuration, { optional: [] }) : new RTCPeerConnection(configuration, { optional: [] });
myConnection.onicecandidate = function (event) {
console.log("onicecandidate");
if (event.candidate) send({ type: "candidate", candidate: event.candidate });
};
myConnection.ontrack=function(e){
try{remoteVideo.src = window.webkitURL?window.webkitURL.createObjectURL(e.streams[0]):window.URL.createObjectURL(e.streams[0])}
catch(err){remoteVideo.srcObject=e.streams[0]}
}
myConnection.ondatachannel=openDataChannel
openDataChannel();
startAVStream();
//startSStream()
};
function startAVStream(enable){
if(sStream)sStream.getTracks().forEach( function (track) {
try{myConnection.removeTrack( track, sStream );}
catch(e){}
} );
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(s => {
if(!avStream){
avStream = s;
avStream.getTracks().forEach( function (track) {
myConnection.addTrack( track, avStream );
} );
}
}, function (error) { console.log(error); });
}
function startSStream(enable){
if(avStream)avStream.getTracks().forEach( function (track) {
try{myConnection.removeTrack( track, avStream );}
catch(e){}
} );
navigator.mediaDevices.getDisplayMedia({ video: true }).then(s => {
if(!sStream){
sStream = s;
sStream.getTracks().forEach( function (track) {
myConnection.addTrack( track, sStream );
} );
}
}, function (error) { console.log(error); });
}
Can anyone tell me how I can switch between tracks inside the same RTCPeerConnection or should I create 2 separate RTCPeerConnection - one for video/audio streaming and another for screen sharing?
Any help appreciated! Thanks!
You could use RTCRtpSender.replaceTrack to splice the screen capture track. This doesn't require renegotiation, and therefore has very low latency.
let newstream = navigator.mediaDevices.getDisplayMedia({});
let newtrack = newstream.getTracks()[1];
if(newtrack.kind !== 'video')
throw new Error('Eek!?');
pc.getSenders().forEach(async s => {
if(s.track && s.track.kind === 'video')
await s.replaceTrack(newtrack);
});
The test for s.track not being null deals with the case where you previously called replaceTrack(..., null).
shareScreen = () =>{
const success = (stream) => {
window.localStream = stream
// this.localVideoref.current.srcObject = stream
// localStream.replaceStream(stream);
this.setState({
localStream: stream
})
Object.values(this.state.peerConnections).forEach(pc => {
pc.getSenders().forEach(async s => {
console.log("s.track ",s.track);
if(s.track && s.track.kind === 'video'){
stream.getTracks().forEach(track => {
// pc.addTrack(track, this.state.localStream)
s.replaceTrack(track);
});
}
});
});
}
const failure = (e) => {
console.log('getUserMedia Error: ', e)
}
navigator.mediaDevices.getDisplayMedia({ cursor: true }).then(success).catch(failure)}

FetchEvent.respondWith received an error: Returned response is null

I created a pwa site which is working totally fine in android devices both online and offline. But it is throwing error FetchEvent.respondWith received an error: Returned response is null when tried to load on IOS device with safari 13.0 if the mobile device is offline.
Here is my code snippet from service_worker.js
// install event
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then((cache) => {
return cache.addAll(assets);
})
);
});
// activate event
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== staticCacheName) {
return caches.delete(key);
}
}));
})
);
});
//fetch event
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request,{ignoreSearch: true});
})
);
});
Please, help me find the solution.

React Native IOS App crashes when no network conection

On the simulator it does not crash and Alerts the error, but in production it is crashes as soon as fetch request suppose to be made and it is impossible to reopen the app until network connection is back (I turn on/off airplane mode for the testing)
here are the snippets of my code
componentWillMount: function(){
NetInfo.isConnected.addEventListener('change',this.handleConnectivityChange)
NetInfo.isConnected.fetch().done((data) => {
this.setState({
isConnected: data
})
console.log('this.state.isConnected: ', this.state.isConnected);
})
},
handleConnectivityChange: function(){
var connected = this.state.isConnected ? false : true
this.setState({isConnected: connected})
console.log('this.state.isConnected11: ', this.state.isConnected);
},
....
goToList: function(replace, listview){
console.log('this.state.isConnected: ', this.props.isConnected);
if (!this.props.isConnected){
AlertIOS.alert('Error', 'Please check your network connectivity')
this.props.removeFetching()
return
}
....
fetch(url)
.then((response) => response.json())
.then((responseData) => {
....
.catch((error) => {
StatusBarIOS.setNetworkActivityIndicatorVisible(false)
AlertIOS.alert('Error', 'Please check your network connectivity')
this.props.removeFetching()
})
.done()
I spent a lot of time trying to find a way to catch exceptions when using fetch() but I was unable to get it working (e.g. using .catch() or a try/catch blog didn't work). What did work was to use XMLHttpRequest with a try/catch blog instead of fetch(). Here's an example I based off of: https://facebook.github.io/react-native/docs/network.html#using-other-networking-libraries
var request = new XMLHttpRequest();
request.onreadystatechange = (e) => {
if (request.readyState !== 4) {
return;
}
if (request.status === 200) {
console.log('success', request.responseText);
var responseJson = JSON.parse(request.responseText);
// *use responseJson here*
} else {
console.warn('error');
}
};
try {
request.open('GET', 'https://www.example.org/api/something');
request.send();
} catch (error) {
console.error(error);
}

Resources