diff --git a/app/assets/styles/blink/_AudioCallBox.scss b/app/assets/styles/blink/_AudioCallBox.scss
index e735706..f999d26 100644
--- a/app/assets/styles/blink/_AudioCallBox.scss
+++ b/app/assets/styles/blink/_AudioCallBox.scss
@@ -1,46 +1,53 @@
.container {
flex: 1;
}
.userIconContainer {
padding-top: 50px;
margin: 0 auto;
}
.buttonContainer {
justify-self: flex-end;
flex-direction: row;
margin: 0 auto;
margin-top:auto;
- bottom: 30;
+ bottom: 20;
}
.button {
background-color: white;
margin: 10px;
padding-top: 5px;
padding-left: 0px;
}
.iosButton {
background-color: rgba(#F9F9F9, .7);
margin: 10px;
padding-top: 5px;
}
.androidButton {
background-color: rgba(#F9F9F9, .7);
margin: 10px;
padding-top: 1px;
}
.hangupButton {
background-color: rgba(#a94442, .8);
}
-.address {
- padding: 0px;
+.displayName {
+ padding-top: 10px;
font-size: 30px;
text-align: center;
color: white;
}
+.uri {
+ padding: 0px;
+ font-size: 18px;
+ text-align: center;
+ color: white;
+}
+
diff --git a/app/components/AudioCallBox.js b/app/components/AudioCallBox.js
index 004e14d..a9defb3 100644
--- a/app/components/AudioCallBox.js
+++ b/app/components/AudioCallBox.js
@@ -1,218 +1,230 @@
import React, { Component } from 'react';
import { View, Platform } from 'react-native';
-import { IconButton, Dialog } from 'react-native-paper';
+import { IconButton, Dialog, Text } from 'react-native-paper';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
import Logger from "../../Logger";
import CallOverlay from './CallOverlay';
import DTMFModal from './DTMFModal';
import EscalateConferenceModal from './EscalateConferenceModal';
import UserIcon from './UserIcon';
+import utils from '../utils';
+
import styles from '../assets/styles/blink/_AudioCallBox.scss';
const logger = new Logger("AudioCallBox");
class AudioCallBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
active : false,
audioMuted : false,
showDtmfModal : false,
showEscalateConferenceModal : false
};
// this.speechEvents = null;
this.remoteAudio = React.createRef();
}
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.props.call != null) {
switch (this.props.call.state) {
case 'established':
this.attachStream(this.props.call);
break;
case 'incoming':
this.props.mediaPlaying();
// fall through
default:
this.props.call.on('stateChanged', this.callStateChanged);
break;
}
} else {
this.props.mediaPlaying();
}
+
}
componentWillReceiveProps(nextProps) {
if (this.props.call == null && nextProps.call) {
if (nextProps.call.state === 'established') {
this.attachStream(nextProps.call);
} else {
nextProps.call.on('stateChanged', this.callStateChanged);
}
}
}
componentWillUnmount() {
clearTimeout(this.callTimer);
// if (this.speechEvents !== null) {
// this.speechEvents.stop();
// this.speechEvents = null;
// }
}
callStateChanged(oldState, newState, data) {
if (newState === 'established') {
this.attachStream(this.props.call);
}
}
attachStream(call) {
this.setState({stream: call.getRemoteStreams()[0]}); //we dont use it anywhere though as audio gets automatically piped
// const options = {
// interval: 225,
// play: false
// };
// this.speechEvents = hark(remoteStream, options);
// this.speechEvents.on('speaking', () => {
// this.setState({active: true});
// });
// this.speechEvents.on('stopped_speaking', () => {
// this.setState({active: false});
// });
}
escalateToConference(participants) {
this.props.escalateToConference(participants);
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall();
}
muteAudio(event) {
event.preventDefault();
//const localStream = this.props.call.getLocalStreams()[0];
if(this.state.audioMuted) {
logger.debug('Unmute microphone');
this.props.callKeepToggleMute(false);
//localStream.getAudioTracks()[0].enabled = true;
this.setState({audioMuted: false});
} else {
logger.debug('Mute microphone');
//localStream.getAudioTracks()[0].enabled = false;
this.props.callKeepToggleMute(true);
this.setState({audioMuted: true});
}
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
toggleEscalateConferenceModal() {
this.setState({
showEscalateConferenceModal: !this.state.showEscalateConferenceModal
});
}
+
render() {
+
let remoteIdentity;
- const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton;
if (this.props.call !== null) {
remoteIdentity = this.props.call.remoteIdentity;
} else {
- remoteIdentity = {uri: this.props.remoteIdentity};
+ remoteIdentity = {uri: this.props.remoteUri};
}
+ const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton;
+
return (
- {this.props.remoteIdentity}
+ {this.props.remoteDisplayName}
+
+ { (this.props.remoteUri !== this.props.remoteDisplayName) ?
+ {this.props.remoteUri}
+ : null }
+
);
}
}
AudioCallBox.propTypes = {
call : PropTypes.object,
escalateToConference : PropTypes.func,
hangupCall : PropTypes.func,
mediaPlaying : PropTypes.func,
- remoteIdentity : PropTypes.string,
+ remoteUri : PropTypes.string,
+ remoteDisplayName : PropTypes.string,
callKeepSendDtmf : PropTypes.func,
callKeepToggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool
};
export default AudioCallBox;
diff --git a/app/components/Call.js b/app/components/Call.js
index 8ef576c..8624557 100644
--- a/app/components/Call.js
+++ b/app/components/Call.js
@@ -1,196 +1,204 @@
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 Logger from "../../Logger";
import AudioCallBox from './AudioCallBox';
import LocalMedia from './LocalMedia';
import VideoBox from './VideoBox';
import config from '../config';
const logger = new Logger("Call");
class Call extends Component {
constructor(props) {
super(props);
autoBind(this);
if (this.props.localMedia && this.props.localMedia.getVideoTracks().length === 0) {
logger.debug('Will send audio only');
this.state = {audioOnly: true};
} else {
this.state = {audioOnly: false};
}
// If current call is available on mount we must have incoming
if (this.props.currentCall != null) {
this.props.currentCall.on('stateChanged', this.callStateChanged);
}
}
componentWillReceiveProps(nextProps) {
// Needed for switching to incoming call while in a call
if (this.props.currentCall != null && this.props.currentCall != nextProps.currentCall) {
if (nextProps.currentCall != null) {
nextProps.currentCall.on('stateChanged', this.callStateChanged);
} else {
this.props.currentCall.removeListener('stateChanged', this.callStateChanged);
}
}
}
callStateChanged(oldState, newState, data) {
// console.log('Call: callStateChanged', newState, '->', newState);
if (newState === 'established') {
// Check the media type again, remote can choose to not accept all offered media types
const currentCall = this.props.currentCall;
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();
}
call() {
assert(this.props.currentCall === null, 'currentCall is not null');
//console.log('Call: starting call', this.props.callUUID, 'to', this.props.targetUri);
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);
call.on('stateChanged', this.callStateChanged);
}
answerCall() {
console.log('Call: answer call');
assert(this.props.currentCall !== null, 'currentCall is null');
let options = {pcConfig: {iceServers: config.iceServers}};
options.localStream = this.props.localMedia;
this.props.currentCall.answer(options);
}
hangupCall() {
console.log('Call: hangup call');
let callUUID = this.props.currentCall._callkeepUUID;
this.props.hangupCall(callUUID);
}
mediaPlaying() {
if (this.props.currentCall === null) {
this.call();
} else {
this.answerCall();
}
}
render() {
//console.log('Call: render');
let box = null;
- let remoteIdentity;
+
+ let remoteUri;
+ let remoteDisplayName;
if (this.props.currentCall !== null) {
- remoteIdentity = this.props.currentCall.remoteIdentity.displayName || this.props.currentCall.remoteIdentity.uri;
+ remoteUri = this.props.currentCall.remoteIdentity.uri;
+ remoteDisplayName = this.props.currentCall.remoteIdentity.displayName || this.props.currentCall.remoteIdentity.uri;
+
} else {
- remoteIdentity = this.props.targetUri;
+ remoteUri = this.props.targetUri;
+ remoteDisplayName = this.props.targetUri;
}
if (this.props.localMedia !== null) {
if (this.state.audioOnly) {
box = (
);
} else {
if (this.props.currentCall != null && this.props.currentCall.state === 'established') {
box = (
);
} else {
if (this.props.currentCall && this.props.currentCall.state && this.props.currentCall.state === 'terminated') {
// do not render
} else {
box = (
);
}
}
}
}
return box;
}
}
Call.propTypes = {
account : PropTypes.object.isRequired,
hangupCall : PropTypes.func.isRequired,
shareScreen : PropTypes.func,
currentCall : PropTypes.object,
escalateToConference : PropTypes.func,
localMedia : PropTypes.object,
targetUri : PropTypes.string,
generatedVideoTrack : PropTypes.bool,
callKeepSendDtmf : PropTypes.func,
callKeepToggleMute : PropTypes.func,
speakerphoneOn : PropTypes.func,
speakerphoneOff : PropTypes.func,
callUUID : PropTypes.string
};
export default Call;
diff --git a/app/components/UserIcon.js b/app/components/UserIcon.js
index 2728e58..b7950c8 100644
--- a/app/components/UserIcon.js
+++ b/app/components/UserIcon.js
@@ -1,67 +1,67 @@
import React, { useEffect, useState } from'react';
import PropTypes from 'prop-types';
import utils from '../utils';
import { Avatar } from 'react-native-paper';
const UserIcon = (props) => {
const [photo, setPhoto] = useState('');
useEffect(() => {
// You need to restrict it at some point
// This is just dummy code and should be replaced by actual
if (!photo && props.identity.uri) {
getPhoto();
}
}, []);
const getPhoto = async () => {
try {
let contacts = await utils.findContact(props.identity.uri);
console.log('contacts', contacts)
contacts.some((contact) => {
if (contact.hasThumbnail) {
setPhoto(contact.thumbnailPath);
return true;
}
});
} catch (err) {
console.log('error getting contacts', err);
}
}
const name = props.identity.displayName || props.identity.uri;
let initials = name.split(' ', 2).map(x => x[0]).join('');
const color = utils.generateMaterialColor(props.identity.uri)['300'];
const avatarSize = props.large ? 120: 60;
if (photo) {
console.log('got an image', photo)
- return
+ return
}
if (props.identity.uri.search('anonymous') !== -1) {
return (
-
+
)
}
if (props.identity.uri.search('videoconference') !== -1) {
return (
-
+
)
}
return (
);
};
UserIcon.propTypes = {
identity: PropTypes.object.isRequired,
large: PropTypes.bool,
card: PropTypes.bool,
active: PropTypes.bool
};
export default UserIcon;
diff --git a/app/components/VideoBox.js b/app/components/VideoBox.js
index 4744d08..8d4d172 100644
--- a/app/components/VideoBox.js
+++ b/app/components/VideoBox.js
@@ -1,402 +1,404 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import debug from 'react-native-debug';
import autoBind from 'auto-bind';
import { IconButton } from 'react-native-paper';
import { View, Dimensions, TouchableOpacity, 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 dtmf from 'react-native-dtmf';
import styles from '../assets/styles/blink/_VideoBox.scss';
const DEBUG = debug('blinkrtc:Video');
debug.enable('*');
class VideoBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
callOverlayVisible: true,
audioMuted: false,
videoMuted: false,
localVideoShow: false,
remoteVideoShow: false,
remoteSharesScreen: false,
showEscalateConferenceModal: false,
localStream: null,
remoteStream: null,
showDtmfModal: false,
doorOpened: false
};
this.overlayTimer = null;
this.localVideo = React.createRef();
this.remoteVideo = React.createRef();
}
callStateChanged(oldState, newState, data) {
DEBUG(`Call state changed ${oldState} -> ${newState}`);
if (newState === 'established') {
this.forceUpdate();
}
}
openDoor() {
const tone = config.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.props.call !== null && this.props.call.state === 'established') {
this.props.call.sendDtmf(tone);
/*this.props.notificationCenter.postSystemNotification('Door opened', {timeout: 5});*/
}
}
componentDidMount() {
console.log('localStreams', this.props.call.getLocalStreams());
console.log('remoteStreams', this.props.call.getRemoteStreams());
this.setState({localStream: this.props.call.getLocalStreams()[0], localVideoShow: true, remoteStream: this.props.call.getRemoteStreams()[0], remoteVideoShow: true});
this.props.call.on('stateChanged', this.callStateChanged);
// sylkrtc.utils.attachMediaStream(, this.localVideo.current, {disableContextMenu: true});
// let promise = this.localVideo.current.play()
// if (promise !== undefined) {
// promise.then(_ => {
// this.setState({localVideoShow: true}); // eslint-disable-line react/no-did-mount-set-state
// // Autoplay started!
// }).catch(error => {
// // Autoplay was prevented.
// // Show a "Play" button so that user can start playback.
// });
// } else {
// this.localVideo.current.addEventListener('playing', () => {
// this.setState({}); // eslint-disable-line react/no-did-mount-set-state
// });
// }
// this.remoteVideo.current.addEventListener('playing', this.handleRemoteVideoPlaying);
// sylkrtc.utils.attachMediaStream(this.props.call.getRemoteStreams()[0], this.remoteVideo.current, {disableContextMenu: true});
}
componentWillUnmount() {
// clearTimeout(this.overlayTimer);
// this.remoteVideo.current.removeEventListener('playing', this.handleRemoteVideoPlaying);
// this.exitFullscreen();
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
handleFullscreen(event) {
event.preventDefault();
// this.toggleFullscreen();
}
handleRemoteVideoPlaying() {
this.setState({remoteVideoShow: true});
// this.remoteVideo.current.onresize = (event) => {
// this.handleRemoteResize(event)
// };
// this.armOverlayTimer();
}
handleRemoteResize(event, target) {
//DEBUG("%o", event);
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();
const localStream = this.state.localStream;
if (localStream.getAudioTracks().length > 0) {
//const track = localStream.getAudioTracks()[0];
if(this.state.audioMuted) {
DEBUG('Unmute microphone');
//track.enabled = true;
this.props.callKeepToggleMute(false);
this.setState({audioMuted: false});
} else {
DEBUG('Mute microphone');
// track.enabled = false;
this.props.callKeepToggleMute(true);
this.setState({audioMuted: true});
}
}
}
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();
}
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall();
}
escalateToConference(participants) {
this.props.escalateToConference(participants);
}
armOverlayTimer() {
clearTimeout(this.overlayTimer);
this.overlayTimer = setTimeout(() => {
this.setState({callOverlayVisible: false});
}, 4000);
}
showCallOverlay() {
if (this.state.remoteVideoShow) {
this.setState({callOverlayVisible: true});
this.armOverlayTimer();
}
}
toggleEscalateConferenceModal() {
this.setState({
callOverlayVisible : false,
showEscalateConferenceModal : !this.state.showEscalateConferenceModal
});
}
render() {
if (this.props.call == null) {
return null;
}
const localVideoClasses = classNames({
'video-thumbnail' : true,
'mirror' : !this.props.call.sharingScreen && !this.props.generatedVideoTrack,
'hidden' : !this.state.localVideoShow,
'animated' : true,
'fadeIn' : this.state.localVideoShow || this.state.videoMuted,
'fadeOut' : this.state.videoMuted,
'fit' : this.props.call.sharingScreen
});
const remoteVideoClasses = classNames({
'poster' : !this.state.remoteVideoShow,
'animated' : true,
'fadeIn' : this.state.remoteVideoShow,
'large' : true,
'fit' : this.state.remoteSharesScreen
});
let callButtons;
let watermark;
if (this.state.callOverlayVisible) {
// const screenSharingButtonIcons = classNames({
// 'fa' : true,
// 'fa-clone' : true,
// 'fa-flip-horizontal' : true,
// 'text-warning' : this.props.call.sharingScreen
// });
// const fullScreenButtonIcons = classNames({
// 'fa' : true,
// 'fa-expand' : !this.isFullScreen(),
// 'fa-compress' : this.isFullScreen()
// });
// const commonButtonClasses = classNames({
// 'btn' : true,
// 'btn-round' : true,
// 'btn-default' : true
// });
const buttons = [];
// buttons.push();
// if (this.isFullscreenSupported()) {
// buttons.push();
// }
// buttons.push(
);
// callButtons = (
// //
// //
// );
} else {
// watermark = (
//
//
//
// );
}
console.log('local media stream in videobox', this.state);
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;
return (
{/*onMouseMove={this.showCallOverlay}*/}
{/* */}
{/* {watermark} */}
{/* */}
{this.state.remoteVideoShow ?
: null }
{ this.state.localVideoShow ?
: null }
{ config.intercomDtmfTone ?
:
}
);
}
}
VideoBox.propTypes = {
call : PropTypes.object,
+ remoteUri : PropTypes.string,
+ remoteDisplayName : PropTypes.string,
localMedia : PropTypes.object,
hangupCall : PropTypes.func,
shareScreen : PropTypes.func,
escalateToConference : PropTypes.func,
generatedVideoTrack : PropTypes.bool,
callKeepSendDtmf : PropTypes.func,
callKeepToggleMute : PropTypes.func,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool
};
export default VideoBox;