diff --git a/app/components/Call.js b/app/components/Call.js
index 84b1ebf..3e11003 100644
--- a/app/components/Call.js
+++ b/app/components/Call.js
@@ -1,467 +1,467 @@
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 = 'outgoing';
let callEnded = false;
this.mediaIsPlaying = false;
this.ended = 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;
}
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,
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;
}
if (nextProps.call !== null && 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 (this.state.direction === 'outgoing' && nextProps.call.direction === 'incoming') {
this.mediaPlaying();
}
this.lookupContact();
}
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});
}
if (nextProps.localMedia !== this.state.localMedia) {
let audioOnly = false;
if (nextProps.localMedia && nextProps.localMedia.getVideoTracks().length === 0) {
audioOnly = true;
}
this.setState({localMedia: nextProps.localMedia,
audioOnly: audioOnly});
}
}
mediaPlaying() {
if (this.state.direction === 'incoming') {
this.answerCall();
} else {
this.mediaIsPlaying = true;
}
}
componentDidMount() {
this.lookupContact();
if (this.state.direction === 'outgoing') {
this.startCallWhenReady();
}
}
componentWillUnmount() {
this.ended = true;
if (this.state.call) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
if (this.props.connection) {
this.props.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);
let remoteHasNoVideoTracks;
let remoteIsRecvOnly;
let remoteIsInactive;
let remoteStreams;
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) {
utils.timestampedLog('Call: connection state changed:', 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;
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.state.callUUID || !this.state.targetUri) {
return;
}
utils.timestampedLog('Call: start call', this.state.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) {
utils.timestampedLog('Call: terminating call', this.state.callUUID, 'that did not start yet');
this.hangupCall('timeout');
}
if (!this.props.connection ||
this.props.connection.state !== 'ready' ||
this.props.registrationState !== 'registered' ||
!this.mediaIsPlaying
) {
utils.timestampedLog('Call: waiting for connection', this.waitInterval - this.waitCounter, 'seconds');
if (this.state.call && this.state.call.state !== 'terminated') {
return;
}
await this._sleep(1000);
} else {
this.waitCounter = 0;
this.call();
return;
}
this.waitCounter++;
}
}
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
call() {
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);
}
}
answerCall() {
if (this.state.call && this.state.call.state === 'incoming') {
let options = {pcConfig: {iceServers: config.iceServers}};
options.localStream = this.state.localMedia;
this.state.call.answer(options);
}
}
hangupCall(reason) {
let callUUID = this.state.call ? this.state.call.id : this.state.callUUID;
this.waitInterval = this.defaultWaitInterval;
this.state.callUUID || this.state.call.id;
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 = (
);
}
}
}
}
return box;
}
}
Call.propTypes = {
targetUri : PropTypes.string.isRequired,
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;
diff --git a/ios/sylkUITests/Info.plist b/ios/sylkUITests/Info.plist
new file mode 100644
index 0000000..64d65ca
--- /dev/null
+++ b/ios/sylkUITests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/ios/sylkUITests/sylkUITests.m b/ios/sylkUITests/sylkUITests.m
new file mode 100644
index 0000000..a293428
--- /dev/null
+++ b/ios/sylkUITests/sylkUITests.m
@@ -0,0 +1,48 @@
+//
+// sylkUITests.m
+// sylkUITests
+//
+// Created by GA Georgescu on 24/08/2020.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+#import
+
+@interface sylkUITests : XCTestCase
+
+@end
+
+@implementation sylkUITests
+
+- (void)setUp {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ self.continueAfterFailure = NO;
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+}
+
+- (void)tearDown {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+}
+
+- (void)testExample {
+ // UI tests must launch the application that they test.
+ XCUIApplication *app = [[XCUIApplication alloc] init];
+ [app launch];
+
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+}
+
+- (void)testLaunchPerformance {
+ if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
+ // This measures how long it takes to launch your application.
+ [self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{
+ [[[XCUIApplication alloc] init] launch];
+ }];
+ }
+}
+
+@end