diff --git a/app/components/Call.js b/app/components/Call.js index 7f67fa0..a75ef9c 100644 --- a/app/components/Call.js +++ b/app/components/Call.js @@ -1,395 +1,395 @@ 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 Logger from "../../Logger"; import AudioCallBox from './AudioCallBox'; import LocalMedia from './LocalMedia'; import VideoBox from './VideoBox'; import config from '../config'; import utils from '../utils'; const logger = new Logger("Call"); class Call extends Component { constructor(props) { super(props); autoBind(this); this.waitCounter = 0; - this.waitInterval = 20; + this.waitInterval = 60; let audioOnly = false; if (this.props.localMedia && this.props.localMedia.getVideoTracks().length === 0) { //logger.debug('Will send audio only'); audioOnly = true; } let remoteUri = ''; let remoteDisplayName = ''; let callState = null; let direction = 'outgoing'; let callUUID; if (this.props.call !== null) { // If current call is available on mount we must have incoming this.props.call.on('stateChanged', this.callStateChanged); callState = this.props.call.state; remoteUri = this.props.call.remoteIdentity.uri; direction = this.props.call.direction; callUUID = this.props.call.id; remoteDisplayName = this.props.call.remoteIdentity.displayName; } else { remoteUri = this.props.targetUri; remoteDisplayName = this.props.targetUri; callUUID = this.props.callUUID; } if (this.props.connection) { this.props.connection.on('stateChanged', this.connectionStateChanged); } this.state = { audioOnly: audioOnly, remoteUri: remoteUri, remoteDisplayName: remoteDisplayName, connection: this.props.connection, accountId: this.props.account ? this.props.account.id : null, callState: callState, direction: direction, callUUID: callUUID } } lookupContact() { let remoteUri = ''; let remoteDisplayName = ''; if (this.props.call !== null) { remoteUri = this.props.call.remoteIdentity.uri; remoteDisplayName = this.props.call.remoteIdentity.displayName || this.props.call.remoteIdentity.uri; //console.log('Incoming call remoteUri', remoteUri); //console.log('Incoming call remoteDisplayName', remoteDisplayName); } else { remoteUri = this.props.targetUri; remoteDisplayName = this.props.targetUri; } 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; this.setState({remoteDisplayName: remoteDisplayName}); if (isPhoneNumber) { remoteUri = username; } } else { if (isPhoneNumber) { remoteUri = username; remoteDisplayName = username; } } } this.setState({remoteDisplayName: remoteDisplayName, remoteUri: remoteUri, reconnectingCall: false }); } //getDerivedStateFromProps(nextProps, state) { UNSAFE_componentWillReceiveProps(nextProps) { // Needed for switching to incoming call while in a call if (this.props.call != null && this.props.call != nextProps.currentCall) { if (nextProps.currentCall != null) { nextProps.currentCall.on('stateChanged', this.callStateChanged); } } this.setState({reconnectingCall: nextProps.reconnectingCall}); } callStateChanged(oldState, newState, data) { //console.log('Call: callStateChanged', oldState, '->', newState); if (newState === 'established') { // Check the media type again, remote can choose to not accept all offered media types const currentCall = this.props.call; const remoteHasStreams = currentCall.getRemoteStreams().length > 0; const remoteHasNoVideoTracks = currentCall.getRemoteStreams()[0].getVideoTracks().length === 0; const remoteIsRecvOnly = currentCall.remoteMediaDirections.video[0] === 'recvonly'; const remoteIsInactive = currentCall.remoteMediaDirections.video[0] === 'inactive'; if (remoteHasStreams && (remoteHasNoVideoTracks || remoteIsRecvOnly || remoteIsInactive) && !this.state.audioOnly) { //console.log('Media type changed to audio'); // Stop local video if (this.props.localMedia.getVideoTracks().length !== 0) { currentCall.getLocalStreams()[0].getVideoTracks()[0].stop(); } this.setState({audioOnly: true}); this.props.speakerphoneOff(); } else { this.forceUpdate(); } currentCall.removeListener('stateChanged', this.callStateChanged); // Switch to video earlier. The callOverlay has a handle on // 'established'. It starts a timer. To prevent a state updating on // unmounted component we try to switch on 'accept'. This means we get // to localMedia first. } 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.props.localMedia && this.props.localMedia.getVideoTracks().length !== 0) { //console.log('Media type changed to video on accepted'); this.setState({audioOnly: false}); this.props.speakerphoneOn(); } } this.forceUpdate(); } connectionStateChanged(oldState, newState) { utils.timestampedLog('Call: connection state changed:', oldState, '->' , newState); switch (newState) { case 'closed': break; case 'ready': break; case 'disconnected': let callUUID = uuid.v4(); this.setState({callUUID: callUUID}); if (oldState === 'ready' && this.state.direction === 'outgoing') { utils.timestampedLog('Call: reconnecting the call using new UUID', callUUID); this.waitInterval = 20; this.startCallWhenReady(); } 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; } async startCallWhenReady() { if (!this.props.callUUID || !this.props.targetUri) { return; } utils.timestampedLog('Call: start call', this.props.callUUID, 'when ready to', this.props.targetUri); this.waitCounter = 0; utils.timestampedLog('Call: waiting for establishing call', this.waitInterval, 'seconds'); let diff = 0; while (this.waitCounter < this.waitInterval) { if (!this.props.connection || this.props.connection.state !== 'ready' || this.props.account === null) { utils.timestampedLog('Call: waiting for connection', this.waitInterval - this.waitCounter, 'seconds'); await this._sleep(1000); } else { this.waitCounter = 0; this.call(); return; } if (this.waitCounter >= this.waitInterval - 1) { utils.timestampedLog('Call: terminating call', this.props.callUUID, 'that did not start yet'); this.hangupCall('timeout'); } this.waitCounter++; } } _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } call() { //console.log('Call: creating new call', this.props.callUUID); assert(this.props.call === null, 'currentCall is not null'); this.lookupContact(); let options = {pcConfig: {iceServers: config.iceServers}, id: this.props.callUUID}; options.localStream = this.props.localMedia; let call = this.props.account.call(this.props.targetUri, options); console.log('Call: call initiated', call.id); call.on('stateChanged', this.callStateChanged); } answerCall() { //console.log('Call: Answer call'); if (this.props.call && this.props.call.state === 'incoming') { this.lookupContact(); let options = {pcConfig: {iceServers: config.iceServers}}; options.localStream = this.props.localMedia; this.props.call.answer(options); } } hangupCall(reason) { let callUUID = this.props.call ? this.props.call._callkeepUUID : this.state.callUUID; this.waitInterval = 20; this.props.callUUID || this.props.call._callkeepUUID; console.log('Call: hangup call', callUUID, 'reason', reason); if (this.props.call) { this.props.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); } mediaPlaying() { if (this.props.call === null && this.props.callUUID) { this.startCallWhenReady(); } else { this.answerCall(); } } render() { /* console.log('Call: render', this.state.direction, 'call', this.props.callUUID); if (this.props.call) { console.log('Call state', this.props.call.state); } else { console.log('Call is null'); } */ let box = null; if (this.props.localMedia !== null) { if (this.state.audioOnly) { box = ( ); } else { if (this.props.call != null && (this.props.call.state === 'established' || this.props.call.state === 'terminated')) { box = ( ); } else { if (this.props.call && this.props.call.state === 'terminated') { // do not render } else { console.log('Render local media'); box = ( ); } } } } return box; } } Call.propTypes = { targetUri : PropTypes.string.isRequired, account : PropTypes.object, hangupCall : PropTypes.func, connection : PropTypes.object, call : PropTypes.object, localMedia : PropTypes.object, shareScreen : PropTypes.func, escalateToConference : PropTypes.func, generatedVideoTrack : PropTypes.bool, callKeepSendDtmf : PropTypes.func, callKeepToggleMute : PropTypes.func, speakerphoneOn : PropTypes.func, speakerphoneOff : PropTypes.func, callUUID : PropTypes.string, contacts : PropTypes.array, intercomDtmfTone : PropTypes.string, orientation : PropTypes.string, isTablet : PropTypes.bool, reconnectingCall : PropTypes.bool }; export default Call;