diff --git a/app/components/AudioCallBox.js b/app/components/AudioCallBox.js
index 5a4d7b4..9fa3f6e 100644
--- a/app/components/AudioCallBox.js
+++ b/app/components/AudioCallBox.js
@@ -1,334 +1,335 @@
import React, { Component } from 'react';
import { View, Platform } from 'react-native';
import { IconButton, Dialog, Text, ActivityIndicator, Colors } from 'react-native-paper';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
import EscalateConferenceModal from './EscalateConferenceModal';
import CallOverlay from './CallOverlay';
import DTMFModal from './DTMFModal';
import UserIcon from './UserIcon';
import styles from '../assets/styles/blink/_AudioCallBox.scss';
import utils from '../utils';
import TrafficStats from './BarChart';
function toTitleCase(str) {
return str.replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
);
}
class AudioCallBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
remoteUri : this.props.remoteUri,
remoteDisplayName : this.props.remoteDisplayName,
photo : this.props.photo,
active : false,
audioMuted : this.props.muted,
showDtmfModal : false,
showEscalateConferenceModal : false,
call : this.props.call,
reconnectingCall : this.props.reconnectingCall,
info : this.props.info,
packetLossQueue : [],
audioBandwidthQueue : [],
latencyQueue : []
};
this.remoteAudio = React.createRef();
this.userHangup = false;
}
componentDidMount() {
// This component is used both for as 'local media' and as the in-call component.
// Thus, if the call is not null it means we are beyond the 'local media' phase
// so don't call the mediaPlaying prop.
if (this.state.call != null) {
switch (this.state.call.state) {
case 'established':
this.attachStream(this.state.call);
break;
case 'incoming':
this.props.mediaPlaying();
// fall through
default:
this.state.call.on('stateChanged', this.callStateChanged);
break;
}
} else {
this.props.mediaPlaying();
}
}
componentWillUnmount() {
if (this.state.call != null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.call !== null && nextProps.call !== this.state.call) {
if (nextProps.call.state === 'established') {
this.attachStream(nextProps.call);
this.setState({reconnectingCall: false});
} else if (nextProps.call.state === 'incoming') {
this.props.mediaPlaying();
}
nextProps.call.on('stateChanged', this.callStateChanged);
if (this.state.call !== null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
this.setState({call: nextProps.call});
}
if (nextProps.reconnectingCall != this.state.reconnectingCall) {
this.setState({reconnectingCall: nextProps.reconnectingCall});
}
if (nextProps.hasOwnProperty('muted')) {
this.setState({audioMuted: nextProps.muted});
}
if (nextProps.hasOwnProperty('info')) {
this.setState({info: nextProps.info});
}
if (nextProps.hasOwnProperty('packetLossQueue')) {
this.setState({packetLossQueue: nextProps.packetLossQueue});
}
if (nextProps.hasOwnProperty('audioBandwidthQueue')) {
this.setState({audioBandwidthQueue: nextProps.audioBandwidthQueue});
}
if (nextProps.hasOwnProperty('latencyQueue')) {
this.setState({latencyQueue: nextProps.latencyQueue});
}
this.setState({remoteUri: nextProps.remoteUri,
remoteDisplayName: nextProps.remoteDisplayName,
photo: nextProps.photo
});
}
componentWillUnmount() {
if (this.state.call != null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
clearTimeout(this.callTimer);
}
callStateChanged(oldState, newState, data) {
if (newState === 'established') {
this.attachStream(this.state.call);
this.setState({reconnectingCall: false});
}
}
attachStream(call) {
this.setState({stream: call.getRemoteStreams()[0]}); //we dont use it anywhere though as audio gets automatically piped
}
escalateToConference(participants) {
this.props.escalateToConference(participants);
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall('user_hangup_call');
this.userHangup = true;
}
cancelCall(event) {
event.preventDefault();
this.props.hangupCall('user_cancelled_call');
}
muteAudio(event) {
event.preventDefault();
this.props.toggleMute(this.props.call.id, !this.state.audioMuted);
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
toggleEscalateConferenceModal() {
this.setState({
showEscalateConferenceModal: !this.state.showEscalateConferenceModal
});
}
render() {
let buttonContainerClass;
let userIconContainerClass;
let remoteIdentity = {uri: this.state.remoteUri || '',
displayName: this.state.remoteDisplayName || '',
photo: this.state.photo
};
let displayName = this.state.remoteUri ? toTitleCase(this.state.remoteUri.split('@')[0]) : '';
if (this.state.remoteDisplayName && this.state.remoteUri !== this.state.remoteDisplayName) {
displayName = this.state.remoteDisplayName;
}
if (this.props.isTablet) {
buttonContainerClass = this.props.orientation === 'landscape' ? styles.tabletLandscapeButtonContainer : styles.tabletPortraitButtonContainer;
userIconContainerClass = styles.tabletUserIconContainer;
} else {
buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer;
userIconContainerClass = styles.userIconContainer;
}
const buttonSize = this.props.isTablet ? 40 : 34;
const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton;
return (
{displayName}
{this.state.remoteUri}
{this.props.orientation !== 'landscape' && this.state.reconnectingCall ?
:
null
}
{this.state.call && ((this.state.call.state === 'accepted' || this.state.call.state === 'established') && !this.state.reconnectingCall) ?
:
}
);
}
}
AudioCallBox.propTypes = {
remoteUri : PropTypes.string,
remoteDisplayName : PropTypes.string,
photo : PropTypes.string,
call : PropTypes.object,
connection : PropTypes.object,
accountId : PropTypes.string,
escalateToConference : PropTypes.func,
info : PropTypes.string,
hangupCall : PropTypes.func,
mediaPlaying : PropTypes.func,
callKeepSendDtmf : PropTypes.func,
toggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool,
orientation : PropTypes.string,
isTablet : PropTypes.bool,
reconnectingCall : PropTypes.bool,
muted : PropTypes.bool,
packetLossQueue : PropTypes.array,
videoBandwidthQueue : PropTypes.array,
audioBandwidthQueue : PropTypes.array,
latencyQueue : PropTypes.array
};
export default AudioCallBox;
diff --git a/app/components/CallOverlay.js b/app/components/CallOverlay.js
index ca402e4..949ca29 100644
--- a/app/components/CallOverlay.js
+++ b/app/components/CallOverlay.js
@@ -1,221 +1,225 @@
import React from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import moment from 'moment';
import momentFormat from 'moment-duration-format';
import autoBind from 'auto-bind';
import { Appbar } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { Colors } from 'react-native-paper';
import styles from '../assets/styles/blink/_AudioCallBox.scss';
function toTitleCase(str) {
return str.replace(
/\w\S*/g,
function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
);
}
class CallOverlay extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
call: this.props.call,
+ media: this.props.media ? this.props.media : 'audio',
callState: this.props.call ? this.props.call.state : null,
direction: this.props.call ? this.props.call.direction: null,
remoteUri: this.props.remoteUri,
remoteDisplayName: this.props.remoteDisplayName,
reconnectingCall: this.props.reconnectingCall
}
this.duration = null;
this.finalDuration = null;
this.timer = null;
this._isMounted = true;
}
componentDidMount() {
if (this.state.call) {
if (this.state.call.state === 'established') {
this.startTimer();
}
this.state.call.on('stateChanged', this.callStateChanged);
this.setState({callState: this.state.call.state});
}
}
componentWillUnmount() {
this._isMounted = false;
if (this.state.call) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
clearTimeout(this.timer);
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (!this._isMounted) {
return;
}
if (nextProps.reconnectingCall != this.state.reconnectingCall) {
this.setState({reconnectingCall: nextProps.reconnectingCall});
}
if (nextProps.call !== null && nextProps.call !== this.state.call) {
nextProps.call.on('stateChanged', this.callStateChanged);
if (this.state.call !== null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
this.setState({call: nextProps.call});
}
this.setState({remoteDisplayName: nextProps.remoteDisplayName,
- remoteUri: nextProps.remoteUri});
+ remoteUri: nextProps.remoteUri,
+ media: nextProps.media
+ });
}
callStateChanged(oldState, newState, data) {
if (newState === 'established' && this._isMounted && !this.props.terminated) {
this.startTimer();
}
if (newState === 'terminated') {
if (this.state.call) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
clearTimeout(this.timer);
this.finalDuration = this.duration;
this.duration = null;
this.timer = null;
}
if (!this._isMounted) {
return;
}
this.setState({callState: newState});
}
startTimer() {
if (this.timer !== null) {
// already armed
return;
}
// TODO: consider using window.requestAnimationFrame
const startTime = new Date();
this.timer = setInterval(() => {
const duration = moment.duration(new Date() - startTime);
if (this.duration > 3600) {
this.duration = duration.format('hh:mm:ss', {trim: false});
} else {
this.duration = duration.format('mm:ss', {trim: false});
}
if (this.props.show) {
this.forceUpdate();
}
}, 1000);
}
render() {
let header = null;
if (this.props.terminated) {
clearTimeout(this.timer);
this.duration = null;
this.timer = null;
}
let displayName = this.state.remoteUri;
if (this.state.remoteDisplayName && this.state.remoteDisplayName !== this.state.remoteUri) {
displayName = this.state.remoteDisplayName;
}
if (this.props.show) {
let callDetail;
if (this.duration) {
callDetail = {this.duration};
callDetail = 'Duration: ' + this.duration;
} else {
if (this.state.reconnectingCall) {
callDetail = 'Reconnecting the call...';
} else if (this.props.terminated) {
callDetail = 'Call ended';
} else if (this.state.callState === 'terminated') {
callDetail = this.finalDuration ? 'Call ended after ' + this.finalDuration : 'Call ended';
} else {
if (this.state.callState) {
if (this.state.callState === 'incoming') {
callDetail = 'Waiting for incoming call...';
} else if (this.state.callState === 'accepted') {
- callDetail = 'Accepted. Waiting for media...';
+ callDetail = 'Waiting for ' + this.state.media + '...';
} else {
callDetail = toTitleCase(this.state.callState);
}
} else if (this.state.direction) {
callDetail = 'Connecting', this.state.direction, 'call...';
} else {
callDetail = 'Connecting...';
}
}
}
if (this.props.info) {
callDetail = callDetail + ' - ' + this.props.info;
}
if (this.state.remoteUri && this.state.remoteUri.search('videoconference') > -1) {
displayName = this.state.remoteUri.split('@')[0];
header = (
);
} else {
header = (
);
}
}
return header
}
}
CallOverlay.propTypes = {
show: PropTypes.bool.isRequired,
remoteUri: PropTypes.string,
remoteDisplayName: PropTypes.string,
call: PropTypes.object,
connection: PropTypes.object,
reconnectingCall: PropTypes.bool,
terminated : PropTypes.bool,
+ media: PropTypes.string,
info: PropTypes.string
};
export default CallOverlay;
diff --git a/app/components/Conference.js b/app/components/Conference.js
index 25236b9..f41d613 100644
--- a/app/components/Conference.js
+++ b/app/components/Conference.js
@@ -1,407 +1,414 @@
import React 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 ConferenceBox from './ConferenceBox';
import LocalMedia from './LocalMedia';
import config from '../config';
import utils from '../utils';
const DEBUG = debug('blinkrtc:Conference');
debug.enable('*');
class Conference extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.defaultWaitInterval = 90; // until we can connect or reconnect
this.waitCounter = 0;
this.waitInterval = this.defaultWaitInterval;
this.userHangup = false;
this.ended = false;
this.started = false;
this.participants = [];
this.state = {
currentCall: null,
callState: null,
targetUri: this.props.targetUri,
callUUID: this.props.callUUID,
localMedia: this.props.localMedia,
connection: this.props.connection,
account: this.props.account,
registrationState: this.props.registrationState,
startedByPush: this.props.startedByPush,
reconnectingCall: this.props.reconnectingCall,
myInvitedParties: this.props.myInvitedParties,
isFavorite: this.props.favoriteUris.indexOf(this.props.targetUri) > -1
}
if (this.props.connection) {
this.props.connection.on('stateChanged', this.connectionStateChanged);
}
if (this.props.participantsToInvite) {
this.props.participantsToInvite.forEach((p) => {
if (this.participants.indexOf(p) === -1) {
this.participants.push(p);
}
});
}
}
componentWillUnmount() {
this.ended = true;
if (this.state.currentCall) {
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
}
if (this.state.connection) {
this.state.connection.removeListener('stateChanged', this.connectionStateChanged);
}
}
callStateChanged(oldState, newState, data) {
//utils.timestampedLog('Conference: callStateChanged', oldState, '->', newState);
if (newState === 'established') {
this.setState({reconnectingCall: false});
}
this.setState({callState: newState});
}
connectionStateChanged(oldState, newState) {
switch (newState) {
case 'disconnected':
if (oldState === 'ready') {
utils.timestampedLog('Conference: connection failed, reconnecting the call...');
this.waitInterval = this.defaultWaitInterval;
}
break;
default:
break;
}
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
//console.log('Conference got props');
if (nextProps.account !== null && nextProps.account !== this.props.account) {
this.setState({account: nextProps.account});
}
this.setState({registrationState: nextProps.registrationState});
if (nextProps.connection !== null && nextProps.connection !== this.state.connection) {
this.setState({connection: nextProps.connection});
nextProps.connection.on('stateChanged', this.connectionStateChanged);
}
if (nextProps.reconnectingCall !== this.state.reconnectingCall) {
this.setState({reconnectingCall: nextProps.reconnectingCall});
}
if (nextProps.localMedia !== null && nextProps.localMedia !== this.state.localMedia) {
this.setState({localMedia: nextProps.localMedia});
}
if (nextProps.callUUID !== null && this.state.callUUID !== nextProps.callUUID) {
this.setState({callUUID: nextProps.callUUID,
reconnectingCall: true,
currentCall: null});
this.startCallWhenReady();
}
this.setState({myInvitedParties: nextProps.myInvitedParties,
isFavorite: nextProps.favoriteUris.indexOf(this.props.targetUri) > -1
});
}
mediaPlaying() {
this.startCallWhenReady();
}
canConnect() {
if (!this.state.localMedia) {
console.log('Conference: no local media');
return false;
}
if (!this.state.connection) {
console.log('Conference: no connection yet');
return false;
}
if (this.state.connection.state !== 'ready') {
console.log('Conference: connection is not ready');
return false;
}
if (!this.state.account) {
console.log('Conference: no account yet');
return false;
}
if (this.state.registrationState !== 'registered') {
console.log('Conference: account not ready yet');
return false;
}
if (this.state.currentCall) {
console.log('Conference: call already in progress');
return false;
}
return true;
}
async startCallWhenReady() {
utils.timestampedLog('Conference: start conference', this.state.callUUID, 'when ready to', this.props.targetUri);
this.waitCounter = 0;
//utils.timestampedLog('Conference: waiting for connecting to the conference', this.waitInterval, 'seconds');
let diff = 0;
while (this.waitCounter < this.waitInterval) {
if (this.userHangup) {
this.props.hangupCall(this.state.callUUID, 'user_cancelled_conference');
return;
}
if (this.state.currentCall) {
return;
}
if (this.waitCounter >= this.waitInterval - 1) {
utils.timestampedLog('Conference: cancelling conference', this.state.callUUID);
this.props.hangupCall(this.state.callUUID, 'timeout');
}
if (!this.canConnect()) {
console.log('Retrying for', (this.waitInterval - this.waitCounter), 'seconds');
await this._sleep(1000);
} else {
this.waitCounter = 0;
this.start();
return;
}
this.waitCounter++;
}
}
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
start() {
const options = {
id: this.state.callUUID,
pcConfig: {iceServers: config.iceServers},
localStream: this.state.localMedia,
audio: this.props.proposedMedia.audio,
video: this.props.proposedMedia.video,
offerOptions: {
offerToReceiveAudio: false,
offerToReceiveVideo: false
},
initialParticipants: this.props.participantsToInvite
};
utils.timestampedLog('Conference: Sylkrtc.js will start conference call', this.state.callUUID, 'to', this.props.targetUri.toLowerCase());
confCall = this.state.account.joinConference(this.props.targetUri.toLowerCase(), options);
if (confCall) {
confCall.on('stateChanged', this.callStateChanged);
this.setState({currentCall: confCall});
}
}
saveParticipant(callUUID, room, uri) {
console.log('Save saveParticipant', uri);
if (this.participants.indexOf(uri) === -1) {
this.participants.push(uri);
}
this.props.saveParticipant(callUUID, room, uri);
}
showSaveDialog() {
if (!this.userHangup) {
return false;
}
if (this.state.reconnectingCall) {
console.log('No save dialog because call is reconnecting')
return false;
}
if (this.participants.length === 0) {
console.log('No show dialog because there are no participants')
return false;
}
if (this.state.isFavorite) {
let room = this.state.targetUri.split('@')[0];
let must_display = false;
if (this.props.myInvitedParties.hasOwnProperty(room)) {
let old_participants = this.state.myInvitedParties[room];
this.participants.forEach((p) => {
if (old_participants.indexOf(p) === -1) {
console.log(p, 'is not in', old_participants);
must_display = true;
}
});
}
if (must_display) {
console.log('Show save dialog because we have new participants');
return true;
} else {
console.log('No save dialog because is already favorite with same participants')
return false;
}
} else {
console.log('Show save dialog because is not in favorites');
return true;
}
return true;
}
saveConference() {
if (!this.state.isFavorite) {
this.props.setFavoriteUri(this.props.targetUri);
}
let room = this.state.targetUri.split('@')[0];
if (this.props.myInvitedParties.hasOwnProperty(room)) {
let participants = this.state.myInvitedParties[room];
this.participants.forEach((p) => {
if (participants.indexOf(p) === -1) {
participants.push(p);
}
});
this.props.saveInvitedParties(this.props.targetUri, participants);
} else {
this.props.saveInvitedParties(this.props.targetUri, this.participants);
}
this.props.hangupCall(this.state.callUUID, 'user_hangup_conference_confirmed');
}
hangup(reason='user_hangup_conference') {
this.userHangup = true;
if (!this.showSaveDialog()) {
reason = 'user_hangup_conference_confirmed';
}
this.props.hangupCall(this.state.callUUID, reason);
if (this.waitCounter > 0) {
this.waitCounter = this.waitInterval;
}
}
render() {
let box = null;
if (this.state.localMedia !== null) {
+ let media = 'audio'
+ if (this.props.proposedMedia && this.props.proposedMedia.video === true) {
+ media = 'audio and video';
+ }
+
if (this.state.currentCall != null && (this.state.callState === 'established')) {
box = (
);
} else {
box = (
+
);
}
} else {
console.log('Error: cannot start conference without local media');
}
return box;
}
}
Conference.propTypes = {
notificationCenter : PropTypes.func,
account : PropTypes.object,
connection : PropTypes.object,
registrationState : PropTypes.string,
hangupCall : PropTypes.func,
saveParticipant : PropTypes.func,
saveInvitedParties : PropTypes.func,
previousParticipants : PropTypes.array,
currentCall : PropTypes.object,
localMedia : PropTypes.object,
targetUri : PropTypes.string,
participantsToInvite : PropTypes.array,
generatedVideoTrack : PropTypes.bool,
toggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
callUUID : PropTypes.string,
proposedMedia : PropTypes.object,
isLandscape : PropTypes.bool,
isTablet : PropTypes.bool,
muted : PropTypes.bool,
defaultDomain : PropTypes.string,
startedByPush : PropTypes.bool,
inFocus : PropTypes.bool,
setFavoriteUri : PropTypes.func,
saveInvitedParties : PropTypes.func,
reconnectingCall : PropTypes.bool,
contacts : PropTypes.array,
favoriteUris : PropTypes.array,
myDisplayNames : PropTypes.object
};
export default Conference;
diff --git a/app/components/LocalMedia.js b/app/components/LocalMedia.js
index cc4fbed..3d3175e 100644
--- a/app/components/LocalMedia.js
+++ b/app/components/LocalMedia.js
@@ -1,144 +1,145 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
import { View, Dimensions } from 'react-native';
import { RTCView } from 'react-native-webrtc';
import { IconButton, Button, Text} from 'react-native-paper';
import CallOverlay from './CallOverlay';
import styles from '../assets/styles/blink/_LocalMedia.scss';
class LocalMedia extends Component {
constructor(props) {
super(props);
autoBind(this);
this.localVideo = React.createRef();
this.state = {
localMedia: this.props.localMedia,
historyEntry: this.props.historyEntry,
participants: this.props.participants,
reconnectingCall: this.props.reconnectingCall
};
}
componentDidMount() {
this.props.mediaPlaying();
}
//getDerivedStateFromProps(nextProps, state)
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.localMedia && nextProps.localMedia !== this.state.localMedia) {
this.props.mediaPlaying();
}
this.setState({historyEntry: nextProps.historyEntry,
participants: nextProps.participants,
reconnectingCall: nextProps.reconnectingCall});
}
saveConference(event) {
event.preventDefault();
this.props.saveConference();
}
showSaveDialog() {
if (!this.props.showSaveDialog) {
return false;
}
return this.props.showSaveDialog();
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall('user_hangup_conference_confirmed');
}
render() {
let {height, width} = Dimensions.get('window');
let videoStyle = {
height,
width
};
const streamUrl = this.props.localMedia ? this.props.localMedia.toURL() : null;
const buttonSize = this.props.isTablet ? 40 : 34;
const buttonContainerClass = this.props.isTablet ? styles.tabletButtonContainer : styles.buttonContainer;
return (
{this.showSaveDialog() ?
Save conference maybe?
Would you like to save participants {this.state.participants.toString().replace(/,/g, ', ')} for having another conference later?
You can find later it in your Favorites.
:
}
);
}
}
LocalMedia.propTypes = {
call : PropTypes.object,
remoteUri : PropTypes.string,
remoteDisplayName : PropTypes.string,
localMedia : PropTypes.object.isRequired,
mediaPlaying : PropTypes.func.isRequired,
hangupCall : PropTypes.func,
showSaveDialog : PropTypes.func,
saveConference : PropTypes.func,
reconnectingCall : PropTypes.bool,
connection : PropTypes.object,
participants : PropTypes.array,
+ media : PropTypes.string,
terminated : PropTypes.bool
};
export default LocalMedia;
diff --git a/app/components/VideoBox.js b/app/components/VideoBox.js
index 9ea10a6..12d2227 100644
--- a/app/components/VideoBox.js
+++ b/app/components/VideoBox.js
@@ -1,405 +1,406 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import dtmf from 'react-native-dtmf';
import debug from 'react-native-debug';
import autoBind from 'auto-bind';
import { IconButton, ActivityIndicator, Colors } from 'react-native-paper';
import { View, Dimensions, TouchableWithoutFeedback, Platform } from 'react-native';
import { RTCView } from 'react-native-webrtc';
import CallOverlay from './CallOverlay';
import EscalateConferenceModal from './EscalateConferenceModal';
import DTMFModal from './DTMFModal';
import config from '../config';
import styles from '../assets/styles/blink/_VideoBox.scss';
//import TrafficStats from './BarChart';
const DEBUG = debug('blinkrtc:Video');
debug.enable('*');
class VideoBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
call: this.props.call,
reconnectingCall: this.props.reconnectingCall,
audioMuted: this.props.muted,
mirror: true,
callOverlayVisible: true,
videoMuted: false,
localVideoShow: true,
remoteVideoShow: true,
remoteSharesScreen: false,
showEscalateConferenceModal: false,
localStream: this.props.call.getLocalStreams()[0],
remoteStream: this.props.call.getRemoteStreams()[0],
info: this.props.info,
showDtmfModal: false,
doorOpened: false,
packetLossQueue : [],
audioBandwidthQueue : [],
latencyQueue : []
};
this.overlayTimer = null;
this.localVideo = React.createRef();
this.remoteVideo = React.createRef();
this.userHangup = false;
}
//getDerivedStateFromProps(nextProps, state) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.hasOwnProperty('muted')) {
this.setState({audioMuted: nextProps.muted});
}
if (nextProps.hasOwnProperty('info')) {
this.setState({info: nextProps.info});
}
if (nextProps.hasOwnProperty('packetLossQueue')) {
this.setState({packetLossQueue: nextProps.packetLossQueue});
}
if (nextProps.hasOwnProperty('audioBandwidthQueue')) {
this.setState({audioBandwidthQueue: nextProps.audioBandwidthQueue});
}
if (nextProps.hasOwnProperty('latencyQueue')) {
this.setState({latencyQueue: nextProps.latencyQueue});
}
if (nextProps.call && nextProps.call !== this.state.call) {
nextProps.call.on('stateChanged', this.callStateChanged);
if (this.state.call !== null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
this.setState({call: nextProps.call,
localStream: nextProps.call.getLocalStreams()[0],
remoteStream: nextProps.call.getRemoteStreams()[0]
});
}
if (nextProps.reconnectingCall != this.state.reconnectingCall) {
this.setState({reconnectingCall: nextProps.reconnectingCall});
}
}
callStateChanged(oldState, newState, data) {
this.forceUpdate();
}
openDoor() {
const tone = this.props.intercomDtmfTone;
DEBUG('DTMF tone sent to intercom: ' + tone);
this.setState({doorOpened: true});
this.forceUpdate();
dtmf.stopTone(); //don't play a tone at the same time as another
dtmf.playTone(dtmf['DTMF_' + tone], 1000);
if (this.state.call !== null && this.state.call.state === 'established') {
this.state.call.sendDtmf(tone);
}
}
componentDidMount() {
if (this.state.call) {
this.state.call.on('stateChanged', this.callStateChanged);
}
this.armOverlayTimer();
}
componentWillUnmount() {
if (this.state.call != null) {
this.state.call.removeListener('stateChanged', this.callStateChanged);
}
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
handleFullscreen(event) {
event.preventDefault();
// this.toggleFullscreen();
}
handleRemoteVideoPlaying() {
this.setState({remoteVideoShow: true});
}
handleRemoteResize(event, target) {
const resolutions = [ '1280x720', '960x540', '640x480', '640x360', '480x270','320x180'];
const videoResolution = event.target.videoWidth + 'x' + event.target.videoHeight;
if (resolutions.indexOf(videoResolution) === -1) {
this.setState({remoteSharesScreen: true});
} else {
this.setState({remoteSharesScreen: false});
}
}
muteAudio(event) {
event.preventDefault();
this.props.toggleMute(this.state.call.id, !this.state.audioMuted);
}
muteVideo(event) {
event.preventDefault();
const localStream = this.state.localStream;
if (localStream.getVideoTracks().length > 0) {
const track = localStream.getVideoTracks()[0];
if(this.state.videoMuted) {
DEBUG('Unmute camera');
track.enabled = true;
this.setState({videoMuted: false});
} else {
DEBUG('Mute camera');
track.enabled = false;
this.setState({videoMuted: true});
}
}
}
toggleCamera(event) {
event.preventDefault();
const localStream = this.state.localStream;
if (localStream.getVideoTracks().length > 0) {
const track = localStream.getVideoTracks()[0];
track._switchCamera();
this.setState({mirror: !this.state.mirror});
}
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall('user_hangup_call');
this.userHangup = true;
}
cancelCall(event) {
event.preventDefault();
this.props.hangupCall('user_cancelled_call');
}
escalateToConference(participants) {
this.props.escalateToConference(participants);
}
armOverlayTimer() {
clearTimeout(this.overlayTimer);
this.overlayTimer = setTimeout(() => {
this.setState({callOverlayVisible: false});
}, 4000);
}
toggleCallOverlay() {
this.setState({callOverlayVisible: !this.state.callOverlayVisible});
}
toggleEscalateConferenceModal() {
this.setState({
callOverlayVisible : false,
showEscalateConferenceModal : !this.state.showEscalateConferenceModal
});
}
render() {
if (this.state.call === null) {
return null;
}
// 'mirror' : !this.state.call.sharingScreen && !this.props.generatedVideoTrack,
// we do not want mirrored local video once the call has started, just in preview
const localVideoClasses = classNames({
'video-thumbnail' : true,
'hidden' : !this.state.localVideoShow,
'animated' : true,
'fadeIn' : this.state.localVideoShow || this.state.videoMuted,
'fadeOut' : this.state.videoMuted,
'fit' : this.state.call.sharingScreen
});
const remoteVideoClasses = classNames({
'poster' : !this.state.remoteVideoShow,
'animated' : true,
'fadeIn' : this.state.remoteVideoShow,
'large' : true,
'fit' : this.state.remoteSharesScreen
});
let buttonContainerClass;
let buttons;
const muteButtonIcons = this.state.audioMuted ? 'microphone-off' : 'microphone';
const muteVideoButtonIcons = this.state.videoMuted ? 'video-off' : 'video';
const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton;
const buttonSize = this.props.isTablet ? 40 : 34;
if (this.props.isTablet) {
buttonContainerClass = this.props.orientation === 'landscape' ? styles.tabletLandscapeButtonContainer : styles.tabletPortraitButtonContainer;
userIconContainerClass = styles.tabletUserIconContainer;
} else {
buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer;
}
if (this.state.callOverlayVisible) {
let content = (
);
if (this.props.intercomDtmfTone) {
content = (
);
}
buttons = ({content});
}
const remoteStreamUrl = this.state.remoteStream ? this.state.remoteStream.toURL() : null
const show = this.state.callOverlayVisible || this.state.reconnectingCall;
return (
{this.state.remoteVideoShow && !this.state.reconnectingCall ?
: null }
{ this.state.localVideoShow ?
: null }
{this.state.reconnectingCall
?
: null
}
{buttons}
);
}
}
VideoBox.propTypes = {
call : PropTypes.object,
connection : PropTypes.object,
photo : PropTypes.string,
accountId : PropTypes.string,
remoteUri : PropTypes.string,
remoteDisplayName : PropTypes.string,
localMedia : PropTypes.object,
hangupCall : PropTypes.func,
info : PropTypes.string,
shareScreen : PropTypes.func,
escalateToConference : PropTypes.func,
generatedVideoTrack : PropTypes.bool,
callKeepSendDtmf : PropTypes.func,
toggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool,
intercomDtmfTone : PropTypes.string,
orientation : PropTypes.string,
isTablet : PropTypes.bool,
reconnectingCall : PropTypes.bool,
muted : PropTypes.bool
};
export default VideoBox;