diff --git a/app/CallManager.js b/app/CallManager.js
index 4a6ac40..b7f913c 100644
--- a/app/CallManager.js
+++ b/app/CallManager.js
@@ -1,607 +1,608 @@
import events from 'events';
import Logger from '../Logger';
import uuid from 'react-native-uuid';
import { Platform, PermissionsAndroid } from 'react-native';
import utils from './utils';
const logger = new Logger('CallManager');
import { CONSTANTS as CK_CONSTANTS } from 'react-native-callkeep';
// https://github.com/react-native-webrtc/react-native-callkeep
/*
const CONSTANTS = {
END_CALL_REASONS: {
FAILED: 1,
REMOTE_ENDED: 2,
UNANSWERED: 3,
ANSWERED_ELSEWHERE: 4,
DECLINED_ELSEWHERE: 5,
MISSED: 6
}
};
*/
const options = {
ios: {
appName: 'Sylk',
maximumCallGroups: 1,
maximumCallsPerCallGroup: 2,
supportsVideo: true,
includesCallsInRecents: true,
imageName: "Image-1"
},
android: {
alertTitle: 'Calling account permission',
alertDescription: 'Please allow Sylk inside All calling accounts',
cancelButton: 'Deny',
okButton: 'Allow',
imageName: 'phone_account_icon',
additionalPermissions: [PermissionsAndroid.PERMISSIONS.CAMERA, PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, PermissionsAndroid.PERMISSIONS.READ_CONTACTS]
}
};
export default class CallManager extends events.EventEmitter {
constructor(RNCallKeep, acceptFunc, rejectFunc, hangupFunc, timeoutFunc, conferenceCallFunc, startCallFromCallKeeper, muteFunc, getConnectionFunct, missedCallFunc, changeRouteFunc, respawnConnection) {
//logger.debug('constructor()');
super();
this.setMaxListeners(Infinity);
this._RNCallKeep = RNCallKeep;
this._calls = new Map();
this._pushCalls = new Map();
this._conferences = new Map();
this._rejectedCalls = new Map();
this._acceptedCalls = new Map();
this._cancelledCalls = new Map();
this._alertedCalls = new Map();
this._terminatedCalls = new Map();
this.webSocketActions = new Map();
this.pushNotificationsActions = new Map();
this._timeouts = new Map();
this.sylkAcceptCall = acceptFunc;
this.sylkRejectCall = rejectFunc;
this.sylkHangupCall = hangupFunc;
this.timeoutCall = timeoutFunc;
this.logMissedCall = missedCallFunc;
this.getConnection = getConnectionFunct;
this.changeRoute = changeRouteFunc;
this.respawnConnection = respawnConnection;
this.toggleMute = muteFunc;
this.conferenceCall = conferenceCallFunc;
this.startCallFromOutside = startCallFromCallKeeper;
this._boundRnAccept = this._rnAccept.bind(this);
this._boundRnEnd = this._rnEnd.bind(this);
this._boundRnMute = this._rnMute.bind(this);
this._boundRnActiveAudioCall = this._rnActiveAudioSession.bind(this);
this._boundRnDeactiveAudioCall = this._rnDeactiveAudioSession.bind(this);
this._boundRnDTMF = this._rnDTMF.bind(this);
this._boundRnProviderReset = this._rnProviderReset.bind(this);
this.boundRnStartAction = this._startedCall.bind(this);
this.boundRnDisplayIncomingCall = this._displayIncomingCall.bind(this);
this._RNCallKeep.addEventListener('answerCall', this._boundRnAccept);
this._RNCallKeep.addEventListener('endCall', this._boundRnEnd);
this._RNCallKeep.addEventListener('didPerformSetMutedCallAction', this._boundRnMute);
this._RNCallKeep.addEventListener('didActivateAudioSession', this._boundRnActiveAudioCall);
this._RNCallKeep.addEventListener('didDeactivateAudioSession', this._boundRnDeactiveAudioCall.bind(this));
this._RNCallKeep.addEventListener('didPerformDTMFAction', this._boundRnDTMF);
this._RNCallKeep.addEventListener('didResetProvider', this._boundRnProviderReset);
this._RNCallKeep.addEventListener('didReceiveStartCallAction', this.boundRnStartAction);
this._RNCallKeep.addEventListener('didDisplayIncomingCall', this.boundRnDisplayIncomingCall);
this._RNCallKeep.setup(options);
this._RNCallKeep.addEventListener('checkReachability', () => {
this._RNCallKeep.setReachable();
});
}
get callKeep() {
return this._RNCallKeep;
}
get countCalls() {
return this._calls.size;
}
get countPushCalls() {
return this._pushCalls.size;
}
get waitingCount() {
return this._timeouts.size;
}
get callUUIDS() {
return Array.from( this._calls.keys() );
}
get calls() {
return [...this._calls.values()];
}
setAvailable(available) {
this.callKeep.setAvailable(available);
}
heartbeat() {
this.callUUIDS.forEach((callUUID) => {
//utils.timestampedLog('Callkeep: call active', callUUID);
});
}
backToForeground() {
//utils.timestampedLog('Callkeep: bring app to the FOREGROUND');
this.callKeep.backToForeground();
}
startOutgoingCall(callUUID, targetUri, hasVideo) {
utils.timestampedLog('Callkeep: WILL START CALL outgoing', callUUID);
if (Platform.OS === 'ios') {
this.callKeep.startCall(callUUID, targetUri, targetUri, 'email', hasVideo);
} else if (Platform.OS === 'android') {
this.callKeep.startCall(callUUID, targetUri, targetUri);
}
}
updateDisplay(callUUID, displayName, uri) {
utils.timestampedLog('Callkeep: update display', displayName, uri);
this.callKeep.updateDisplay(callUUID, displayName, uri);
}
sendDTMF(callUUID, digits) {
utils.timestampedLog('Callkeep: send DTMF: ', digits);
this.callKeep.sendDTMF(callUUID, digits);
}
setCurrentCallActive(callUUID) {
utils.timestampedLog('Callkeep: CALL ACTIVE', callUUID);
this.callKeep.setCurrentCallActive(callUUID);
}
endCalls() {
utils.timestampedLog('Callkeep: end all calls');
this.callKeep.endAllCalls();
}
endCall(callUUID, reason) {
if (reason) {
utils.timestampedLog('Callkeep: END CALL', callUUID, 'with reason', reason);
} else {
utils.timestampedLog('Callkeep: END CALL', callUUID);
}
if (this._pushCalls.has(callUUID)) {
this._pushCalls.delete(callUUID);
}
if (this._rejectedCalls.has(callUUID)) {
// return;
}
if (this._cancelledCalls.has(callUUID)) {
//utils.timestampedLog('Callkeep: CALL', callUUID, 'already cancelled');
return;
}
if (reason === 2) {
this._cancelledCalls.set(callUUID, Date.now());
}
if (reason) {
this.callKeep.reportEndCallWithUUID(callUUID, reason);
if (this._timeouts.has(callUUID)) {
clearTimeout(this._timeouts.get(callUUID));
this._timeouts.delete(callUUID);
}
} else {
this.callKeep.endCall(callUUID);
}
}
terminateCall(callUUID) {
if (this._calls.has(callUUID)) {
this._calls.delete(callUUID);
}
this._terminatedCalls.set(callUUID, Date.now());
if (this._pushCalls.has(callUUID)) {
this._pushCalls.delete(callUUID);
}
}
_rnActiveAudioSession() {
utils.timestampedLog('Callkeep: activated audio call');
}
_rnDeactiveAudioSession() {
utils.timestampedLog('Callkeep: deactivated audio call');
}
_rnAccept(data) {
let callUUID = data.callUUID.toLowerCase();
utils.timestampedLog('Callkeep: accept callback', callUUID);
if (this._pushCalls.has(callUUID)) {
this._pushCalls.delete(callUUID);
}
if (!this._rejectedCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: accept call', callUUID);
this.acceptCall(callUUID);
} else {
utils.timestampedLog('Callkeep: cannot accept because we already rejected', callUUID);
}
}
_rnEnd(data) {
// this is called both when user touches Reject and when the call ends
let callUUID = data.callUUID.toLowerCase();
utils.timestampedLog('Callkeep: end callback', callUUID);
if (this._pushCalls.has(callUUID)) {
this._pushCalls.delete(callUUID);
}
if (this._terminatedCalls.has(callUUID)) {
return;
}
let call = this._calls.get(callUUID);
if (!call && !this._conferences.has(callUUID)) {
utils.timestampedLog('Callkeep: add call', callUUID, 'reject to the waitings list');
this.webSocketActions.set(callUUID, 'reject');
this.changeRoute('/ready', 'new_call_rejected');
return;
}
if (call && call.state === 'incoming') {
if (!this._acceptedCalls.has(callUUID)) {
this.rejectCall(callUUID);
}
} else {
if (this._conferences.has(callUUID)) {
const conference = this._conferences.get(callUUID);
this.logMissedCall(conference.room, callUUID, direction='received', participants=[conference.from]);
this._conferences.delete(callUUID);
} else {
this.sylkHangupCall(callUUID, 'user_press_hangup');
}
}
}
acceptCall(callUUID) {
if (this._acceptedCalls.has(callUUID)) {
//utils.timestampedLog('Callkeep: already accepted call', callUUID);
utils.timestampedLog('Callkeep: accept call again', callUUID);
//return;
} else {
utils.timestampedLog('Callkeep: accept call', callUUID);
}
this.setCurrentCallActive(callUUID);
this._acceptedCalls.set(callUUID, Date.now());
if (this._timeouts.has(callUUID)) {
clearTimeout(this._timeouts.get(callUUID));
this._timeouts.delete(callUUID);
}
if (this._conferences.has(callUUID)) {
let conference = this._conferences.get(callUUID);
utils.timestampedLog('Callkeep: accept incoming conference', callUUID);
this.endCall(callUUID, 4);
this.backToForeground();
utils.timestampedLog('Callkeep: will start conference to', conference.room);
this.conferenceCall(conference.room);
this._conferences.delete(callUUID);
} else if (this._calls.has(callUUID)) {
this.backToForeground();
this.sylkAcceptCall(callUUID);
} else {
this.backToForeground();
utils.timestampedLog('Callkeep: add call', callUUID, 'accept to the waitings list');
// We accepted the call before it arrived on web socket
- if (Platform.OS === 'ios') {
- this.respawnConnection();
- }
+ this.respawnConnection();
this.webSocketActions.set(callUUID, 'accept');
utils.timestampedLog('Callkeep: check over 12 seconds if call', callUUID, 'arrived over web socket');
setTimeout(() => {
const connection = this.getConnection();
if (this.callUUIDS) {
utils.timestampedLog('Callkeep: current calls:', this.callUUIDS);
}
if (!this._calls.has(callUUID) && !this._terminatedCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: call', callUUID, 'did not arrive over web socket', connection);
this.webSocketActions.delete(callUUID);
this.endCall(callUUID, 1);
this.sylkHangupCall(callUUID, 'timeout');
} else {
utils.timestampedLog('Callkeep: call', callUUID, 'did arrive over web socket', connection);
}
}, 12000);
}
}
rejectCall(callUUID) {
if (this._rejectedCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: already rejected call', callUUID);
return;
}
utils.timestampedLog('Callkeep: reject call', callUUID);
this._rejectedCalls.set(callUUID, Date.now());
if (this._timeouts.has(callUUID)) {
clearTimeout(this._timeouts.get(callUUID));
this._timeouts.delete(callUUID);
}
this.callKeep.rejectCall(callUUID);
if (this._conferences.has(callUUID)) {
utils.timestampedLog('Callkeep: reject conference invite', callUUID);
let room = this._conferences.get(callUUID);
this._conferences.delete(callUUID);
} else if (this._calls.has(callUUID)) {
let call = this._calls.get(callUUID);
if (call.state === 'incoming') {
this.sylkRejectCall(callUUID);
} else {
this.sylkHangupCall(callUUID, 'user_press_hangup');
}
} else {
// We rejected the call before it arrived on web socket
// from iOS push notifications
utils.timestampedLog('Callkeep: add call', callUUID, 'reject to the waitings list');
this.webSocketActions.set(callUUID, 'reject');
utils.timestampedLog('Callkeep: check over 20 seconds if call', callUUID, 'arrived on web socket');
setTimeout(() => {
if (!this._calls.has(callUUID)) {
utils.timestampedLog('Callkeep: call', callUUID, 'did not arrive on web socket');
this.webSocketActions.delete(callUUID);
this.endCall(callUUID, 1);
}
}, 20000);
}
this.endCall(callUUID);
}
setMutedCall(callUUID, mute=false) {
//utils.timestampedLog('Callkeep: set call', callUUID, 'muted =', mute);
if (this._calls.has(callUUID)) {
this.callKeep.setMutedCall(callUUID, mute);
let call = this._calls.get(callUUID);
const localStream = call.getLocalStreams()[0];
if (mute) {
utils.timestampedLog('Callkeep: local stream audio track disabled');
} else {
utils.timestampedLog('Callkeep: local stream audio track enabled');
}
localStream.getAudioTracks()[0].enabled = !mute;
}
}
_rnMute(data) {
utils.timestampedLog('Callkeep: mute ' + data.muted + ' for call', data.callUUID);
this.toggleMute(data.callUUID, data.muted);
}
_rnDTMF(data) {
utils.timestampedLog('Callkeep: got dtmf for call', data.callUUID);
if (this._calls.has(data.callUUID.toLowerCase())) {
let call = this._calls.get(data.callUUID.toLowerCase());
utils.timestampedLog('sending webrtc dtmf', data.digits)
call.sendDtmf(data.digits);
}
}
_rnProviderReset() {
utils.timestampedLog('Callkeep: got a provider reset, clearing down all calls');
this._calls.forEach((call) => {
call.terminate();
});
}
addWebsocketCall(call) {
const connection = this.getConnection();
if (this._calls.has(call.id)) {
return;
}
utils.timestampedLog('Callkeep: added call', call.id, 'for connection', connection);
this._calls.set(call.id, call);
}
incomingCallFromPush(callUUID, from, force=false, skipNativePanel=false) {
utils.timestampedLog('Callkeep: handle new incoming push call', callUUID, 'from', from);
if (this._pushCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: push call already handled', callUUID);
return;
}
this._pushCalls.set(callUUID, true);
if (this._rejectedCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: call already rejected', callUUID);
this.endCall(callUUID, CK_CONSTANTS.END_CALL_REASONS.UNANSWERED);
return;
}
if (this._acceptedCalls.has(callUUID)) {
utils.timestampedLog('Callkeep: call already accepted', callUUID);
return;
}
// if user does not decide anything this will be handled later
this._timeouts.set(callUUID, setTimeout(() => {
utils.timestampedLog('Callkeep: incoming call', callUUID, 'timeout');
let reason = this.webSocketActions.has(callUUID) ? CK_CONSTANTS.END_CALL_REASONS.FAILED : CK_CONSTANTS.END_CALL_REASONS.UNANSWERED;
if (!this._terminatedCalls.has(callUUID) && !this._calls.has(callUUID)) {
const connection = this.getConnection();
utils.timestampedLog('Callkeep: call', callUUID, 'did not arive on web socket', connection);
reason = CK_CONSTANTS.END_CALL_REASONS.FAILED;
} else if (this._calls.has(callUUID)) {
utils.timestampedLog('Callkeep: user did not accept or reject', callUUID);
}
this.endCall(callUUID, reason);
this._timeouts.delete(callUUID);
}, 45000));
if (Platform.OS === 'ios') {
if (this._calls.has(callUUID)) {
utils.timestampedLog('Callkeep: call', callUUID, 'already received on web socket');
}
this.showAlertPanel(callUUID, from);
} else {
if (this._calls.has(callUUID) || force) {
// on Android display alert panel only after websocket call arrives
// force is required when Android is locked, if we do not bring up the panel, the app will not wake up
if (!skipNativePanel || force) {
this.showAlertPanel(callUUID, from);
} else {
utils.timestampedLog('Callkeep: call', callUUID, 'skipped display of native panel');
}
} else {
utils.timestampedLog('Callkeep: waiting for call', callUUID, 'on web socket');
}
}
}
incomingCallFromWebSocket(call, accept=false, skipNativePanel=false) {
this.addWebsocketCall(call);
utils.timestampedLog('Callkeep: handle incoming websocket call', call.id);
// if the call came via push and was already accepted or rejected
if (this.webSocketActions.get(call.id)) {
let action = this.webSocketActions.get(call.id);
utils.timestampedLog('Callkeep: execute action decided earlier', action);
if (action === 'accept') {
this.sylkAcceptCall(call.id);
} else {
this.sylkRejectCall(call.id);
}
this.webSocketActions.delete(call.id);
} else {
if (accept) {
this.acceptCall(call.id);
} else if (!skipNativePanel) {
- this.showAlertPanelforCall(call);
+ if (Platform.OS === 'ios') {
+ this.showAlertPanelforCall(call);
+ }
}
}
// Emit event.
this._emitSessionsChange(true);
}
handleConference(callUUID, room, from_uri) {
if (this._conferences.has(callUUID)) {
return;
}
this._conferences.set(callUUID, {room: room, from: from_uri});
utils.timestampedLog('CallKeep: handle conference', callUUID, 'from', from_uri, 'to room', room);
this.showAlertPanel(callUUID, from_uri);
this._timeouts.set(callUUID, setTimeout(() => {
utils.timestampedLog('Callkeep: conference timeout', callUUID);
this.timeoutCall(callUUID, from_uri);
this.endCall(callUUID, CK_CONSTANTS.END_CALL_REASONS.MISSED);
this._timeouts.delete(callUUID);
}, 45000));
this._emitSessionsChange(true);
}
showAlertPanelforCall(call, force=false) {
const callUUID = call.id;
const uri = call.remoteIdentity.uri;
const username = uri.split('@')[0];
const isPhoneNumber = username.match(/^(\+|0)(\d+)$/);
const from = isPhoneNumber ? username: uri;
const hasVideo = call.mediaTypes && call.mediaTypes.video;
this.showAlertPanel(callUUID, from, hasVideo);
}
showAlertPanel(callUUID, uri, hasVideo=false) {
if (this._alertedCalls.has(callUUID)) {
+ //utils.timestampedLog('Callkeep: call', callUUID, 'was already alerted');
return;
}
const username = uri.split('@')[0];
const isPhoneNumber = username.match(/^(\+|0)(\d+)$/);
const from = isPhoneNumber ? username: uri;
utils.timestampedLog('Callkeep: ALERT PANEL for', callUUID, 'from', from);
this._alertedCalls.set(callUUID, Date.now());
if (Platform.OS === 'ios') {
this.callKeep.displayIncomingCall(callUUID, from, from, 'email', hasVideo);
} else if (Platform.OS === 'android') {
this.callKeep.displayIncomingCall(callUUID, from, from);
}
}
_startedCall(data) {
utils.timestampedLog("Callkeep: STARTED CALL", data.callUUID);
if (!this._calls.has(data.callUUID)) {
// call has started from OS native dialer
this.startCallFromOutside(data);
}
}
_displayIncomingCall(data) {
utils.timestampedLog('Callkeep: Incoming alert panel displayed');
}
_emitSessionsChange(countChanged) {
this.emit('sessionschange', countChanged);
}
destroy() {
this._RNCallKeep.removeEventListener('acceptCall', this._boundRnAccept);
this._RNCallKeep.removeEventListener('endCall', this._boundRnEnd);
this._RNCallKeep.removeEventListener('didPerformSetMutedCallAction', this._boundRnMute);
this._RNCallKeep.removeEventListener('didActivateAudioSession', this._boundRnActiveAudioCall);
this._RNCallKeep.removeEventListener('didDeactivateAudioSession', this._boundRnDeactiveAudioCall);
this._RNCallKeep.removeEventListener('didPerformDTMFAction', this._boundRnDTMF);
this._RNCallKeep.removeEventListener('didResetProvider', this._boundRnProviderReset);
this._RNCallKeep.removeEventListener('didReceiveStartCallAction', this.boundRnStartAction);
this._RNCallKeep.removeEventListener('didDisplayIncomingCall', this.boundRnDisplayIncomingCall);
}
}
diff --git a/app/components/Call.js b/app/components/Call.js
index bac97d4..926bcae 100644
--- a/app/components/Call.js
+++ b/app/components/Call.js
@@ -1,546 +1,551 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import assert from 'assert';
import debug from 'react-native-debug';
import autoBind from 'auto-bind';
import uuid from 'react-native-uuid';
import AudioCallBox from './AudioCallBox';
import LocalMedia from './LocalMedia';
import VideoBox from './VideoBox';
import config from '../config';
import utils from '../utils';
class Call extends Component {
constructor(props) {
super(props);
autoBind(this);
this.defaultWaitInterval = 60; // until we can connect or reconnect
this.waitCounter = 0;
this.waitInterval = this.defaultWaitInterval;
let callUUID;
let remoteUri = '';
let remoteDisplayName = '';
let callState = null;
let direction = null;
let callEnded = false;
this.mediaIsPlaying = false;
this.ended = false;
this.answering = false;
if (this.props.call) {
// If current call is available on mount we must have incoming
this.props.call.on('stateChanged', this.callStateChanged);
remoteUri = this.props.call.remoteIdentity.uri;
remoteDisplayName = this.props.call.remoteIdentity.displayName || this.props.call.remoteIdentity.uri;
direction = this.props.call.direction;
callUUID = this.props.call.id;
} else {
remoteUri = this.props.targetUri;
remoteDisplayName = this.props.targetUri;
callUUID = this.props.callUUID;
- direction = this.props.callUUID ? 'outgoing' : null;
+ direction = callUUID ? 'outgoing' : 'incoming';
}
if (this.props.connection) {
this.props.connection.on('stateChanged', this.connectionStateChanged);
}
let audioOnly = false;
if (this.props.localMedia && this.props.localMedia.getVideoTracks().length === 0) {
audioOnly = true;
}
this.state = {
call: this.props.call,
targetUri: this.props.targetUri,
audioOnly: audioOnly,
boo: false,
remoteUri: remoteUri,
remoteDisplayName: remoteDisplayName,
localMedia: this.props.localMedia,
connection: this.props.connection,
accountId: this.props.account ? this.props.account.id : null,
callState: callState,
direction: direction,
callUUID: callUUID,
reconnectingCall: this.props.reconnectingCall
}
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
// Needed for switching to incoming call while in a call
if (this.ended) {
return;
}
+ //console.log('Call got props...');
- this.setState({accountId: nextProps.account ? nextProps.account.id : null});
+ this.setState({connection: nextProps.connection,
+ accountId: nextProps.account ? nextProps.account.id : null});
if (nextProps.call !== null) {
if (this.state.call !== nextProps.call) {
nextProps.call.on('stateChanged', this.callStateChanged);
this.setState({
call: nextProps.call,
remoteUri: nextProps.call.remoteIdentity.uri,
direction: nextProps.call.direction,
callUUID: nextProps.call.id,
remoteDisplayName: nextProps.call.remoteIdentity.displayName
});
if (nextProps.call.direction === 'incoming') {
this.mediaPlaying();
}
this.lookupContact();
}
} else {
if (nextProps.callUUID !== null && this.state.callUUID !== nextProps.callUUID) {
this.setState({'callUUID': nextProps.callUUID,
'direction': 'outgoing',
'call': null
});
this.startCallWhenReady(nextProps.callUUID);
}
}
if (nextProps.reconnectingCall !== this.state.reconnectingCall) {
this.setState({reconnectingCall: nextProps.reconnectingCall});
}
if (nextProps.targetUri !== this.state.targetUri && this.state.direction === 'outgoing') {
this.setState({targetUri: nextProps.targetUri});
}
this.setState({registrationState: nextProps.registrationState});
if (nextProps.localMedia !== null && nextProps.localMedia !== this.state.localMedia) {
let audioOnly = false;
if (nextProps.localMedia.getVideoTracks().length === 0) {
audioOnly = true;
}
this.setState({localMedia: nextProps.localMedia,
audioOnly: audioOnly});
this.mediaPlaying(nextProps.localMedia);
}
}
mediaPlaying(localMedia) {
if (this.state.direction === 'incoming') {
const media = localMedia ? localMedia : this.state.localMedia;
this.answerCall(media);
} else {
this.mediaIsPlaying = true;
}
}
answerCall(localMedia) {
const media = localMedia ? localMedia : this.state.localMedia;
if (this.state.call && this.state.call.state === 'incoming' && media) {
let options = {pcConfig: {iceServers: config.iceServers}};
options.localStream = media;
if (!this.answering) {
this.answering = true;
const connectionState = this.state.connection.state ? this.state.connection.state : null;
utils.timestampedLog('Call: answering call in connection state', connectionState);
this.state.call.answer(options);
} else {
utils.timestampedLog('Call: answering call in progress...');
}
} else {
if (!media) {
utils.timestampedLog('Call: waiting for local media');
}
if (!this.state.call) {
utils.timestampedLog('Call: waiting for incoming call data');
}
}
}
componentDidMount() {
this.lookupContact();
if (this.state.direction === 'outgoing' && this.state.callUUID) {
this.startCallWhenReady(this.state.callUUID);
}
}
componentWillUnmount() {
this.ended = true;
+ this.answering = false;
if (this.state.call) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
if (this.state.connection) {
this.state.connection.removeListener('stateChanged', this.connectionStateChanged);
}
}
lookupContact() {
let photo = null;
let remoteUri = this.state.remoteUri || '';
let remoteDisplayName = this.state.remoteDisplayName || '';
if (!remoteUri) {
return;
}
if (remoteUri.indexOf('3333@') > -1) {
remoteDisplayName = 'Video Test';
} else if (remoteUri.indexOf('4444@') > -1) {
remoteDisplayName = 'Echo Test';
} else if (this.props.contacts) {
let username = remoteUri.split('@')[0];
let isPhoneNumber = username.match(/^(\+|0)(\d+)$/);
if (isPhoneNumber) {
var contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', username);
} else {
var contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', remoteUri);
}
if (contact_obj) {
remoteDisplayName = contact_obj.displayName;
photo = contact_obj.photo;
if (isPhoneNumber) {
remoteUri = username;
}
} else {
if (isPhoneNumber) {
remoteUri = username;
remoteDisplayName = username;
}
}
}
this.setState({remoteDisplayName: remoteDisplayName,
remoteUri: remoteUri,
photo: photo
});
}
callStateChanged(oldState, newState, data) {
//console.log('Call: callStateChanged', oldState, '->', newState);
if (this.ended) {
return;
}
let remoteHasNoVideoTracks;
let remoteIsRecvOnly;
let remoteIsInactive;
let remoteStreams;
+ this.answering = false;
+
if (newState === 'established') {
this.setState({reconnectingCall: false});
const currentCall = this.state.call;
if (currentCall) {
remoteStreams = currentCall.getRemoteStreams();
if (remoteStreams) {
if (remoteStreams.length > 0) {
const remotestream = remoteStreams[0];
remoteHasNoVideoTracks = remotestream.getVideoTracks().length === 0;
remoteIsRecvOnly = currentCall.remoteMediaDirections.video[0] === 'recvonly';
remoteIsInactive = currentCall.remoteMediaDirections.video[0] === 'inactive';
}
}
}
if (remoteStreams && (remoteHasNoVideoTracks || remoteIsRecvOnly || remoteIsInactive) && !this.state.audioOnly) {
//console.log('Media type changed to audio');
// Stop local video
if (this.state.localMedia.getVideoTracks().length !== 0) {
currentCall.getLocalStreams()[0].getVideoTracks()[0].stop();
}
this.setState({audioOnly: true});
} else {
this.forceUpdate();
}
} else if (newState === 'accepted') {
// Switch if we have audioOnly and local videotracks. This means
// the call object switched and we are transitioning to an
// incoming call.
if (this.state.audioOnly && this.state.localMedia && this.state.localMedia.getVideoTracks().length !== 0) {
//console.log('Media type changed to video on accepted');
this.setState({audioOnly: false});
}
}
this.forceUpdate();
}
connectionStateChanged(oldState, newState) {
switch (newState) {
case 'closed':
break;
case 'ready':
break;
case 'disconnected':
if (oldState === 'ready' && this.state.direction === 'outgoing') {
utils.timestampedLog('Call: reconnecting the call...');
this.waitInterval = this.defaultWaitInterval;
}
break;
default:
break;
}
}
findObjectByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
canConnect() {
if (!this.state.connection) {
console.log('Call: no connection yet');
return false;
}
if (this.state.connection.state !== 'ready') {
console.log('Call: connection is not ready');
return false;
}
if (this.props.registrationState !== 'registered') {
console.log('Call: account not ready yet');
return false;
}
if (!this.mediaIsPlaying) {
console.log('Call: media is not playing');
return false;
}
return true;
}
async startCallWhenReady(callUUID) {
utils.timestampedLog('Call: start call', callUUID, 'when ready to', this.state.targetUri);
this.waitCounter = 0;
let diff = 0;
while (this.waitCounter < this.waitInterval) {
if (this.waitCounter === 1) {
utils.timestampedLog('Call: waiting for establishing call', this.waitInterval, 'seconds');
}
if (this.userHangup) {
this.hangupCall('user_cancelled');
return;
}
if (this.ended) {
return;
}
if (this.waitCounter >= this.waitInterval - 1) {
this.hangupCall('timeout');
}
if (!this.canConnect()) {
//utils.timestampedLog('Call: waiting for connection', this.waitInterval - this.waitCounter, 'seconds');
if (this.state.call && this.state.call.id === callUUID && this.state.call.state !== 'terminated') {
return;
}
console.log('Wait', this.waitCounter);
await this._sleep(1000);
} else {
this.waitCounter = 0;
this.start();
return;
}
this.waitCounter++;
}
}
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
start() {
utils.timestampedLog('Call: starting call', this.state.callUUID);
if (this.state.localMedia === null) {
console.log('Call: cannot create new call without local media');
return;
}
let options = {pcConfig: {iceServers: config.iceServers}, id: this.state.callUUID};
options.localStream = this.state.localMedia;
let call = this.props.account.call(this.state.targetUri, options);
if (call) {
call.on('stateChanged', this.callStateChanged);
}
}
hangupCall(reason) {
let callUUID = this.state.call ? this.state.call.id : this.state.callUUID;
this.waitInterval = this.defaultWaitInterval;
if (this.state.call) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
if (this.props.connection) {
this.props.connection.removeListener('stateChanged', this.connectionStateChanged);
}
if (this.waitCounter > 0) {
this.waitCounter = this.waitInterval;
}
this.props.hangupCall(callUUID, reason);
}
render() {
let box = null;
if (this.state.localMedia !== null) {
if (this.state.audioOnly) {
box = (
);
} else {
if (this.state.call !== null && (this.state.call.state === 'established' || (this.state.call.state === 'terminated' && this.state.reconnectingCall))) {
box = (
);
} else {
if (this.state.call && this.state.call.state === 'terminated' && this.state.reconnectingCall) {
//console.log('Skip render local media because we will reconnect');
} else {
box = (
);
}
}
}
} else {
box = (
);
}
return box;
}
}
Call.propTypes = {
targetUri : PropTypes.string,
account : PropTypes.object,
hangupCall : PropTypes.func,
connection : PropTypes.object,
registrationState : PropTypes.string,
call : PropTypes.object,
localMedia : PropTypes.object,
shareScreen : PropTypes.func,
escalateToConference : PropTypes.func,
generatedVideoTrack : PropTypes.bool,
callKeepSendDtmf : PropTypes.func,
toggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool,
callUUID : PropTypes.string,
contacts : PropTypes.array,
intercomDtmfTone : PropTypes.string,
orientation : PropTypes.string,
isTablet : PropTypes.bool,
reconnectingCall : PropTypes.bool,
muted : PropTypes.bool
};
export default Call;