diff --git a/app/app.js b/app/app.js
index 6ef984e..84acfd1 100644
--- a/app/app.js
+++ b/app/app.js
@@ -1,1062 +1,1062 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import { View, SafeAreaView, ImageBackground } from 'react-native';
import { Provider as PaperProvider, DefaultTheme } from 'react-native-paper';
import { BreadProvider } from "material-bread";
import { registerGlobals } from 'react-native-webrtc';
import { Router, Route, Link, Switch } from 'react-router-native';
import history from './history';
import debug from 'react-native-debug';
import DigestAuthRequest from 'digest-auth-request';
import autoBind from 'auto-bind';
debug.enable('*');
registerGlobals();
import * as sylkrtc from 'sylkrtc';
import RegisterBox from './components/RegisterBox';
import ReadyBox from './components/ReadyBox';
import Call from './components/Call';
import CallByUriBox from './components/CallByUriBox';
import Conference from './components/Conference';
import ConferenceByUriBox from './components/ConferenceByUriBox';
// import AudioPlayer from './components/AudioPlayer';
// import ErrorPanel from './components/ErrorPanel';
import FooterBox from './components/FooterBox';
import StatusBox from './components/StatusBox';
import IncomingCallModal from './components/IncomingCallModal';
import NotificationCenter from './components/NotificationCenter';
import LoadingScreen from './components/LoadingScreen';
import NavigationBar from './components/NavigationBar';
import Preview from './components/Preview';
import utils from './utils';
import config from './config';
import storage from './storage';
import styles from './assets/styles/blink/root.scss';
const backgroundImage = require('./assets/images/dark_linen.png');
const theme = {
...DefaultTheme,
dark: true,
roundness: 2,
colors: {
...DefaultTheme.colors,
primary: '#337ab7',
// accent: '#f1c40f',
},
};
const DEBUG = debug('blinkrtc:App');
// Application modes
const MODE_NORMAL = Symbol('mode-normal');
const MODE_PRIVATE = Symbol('mode-private');
const MODE_GUEST_CALL = Symbol('mode-guest-call');
const MODE_GUEST_CONFERENCE = Symbol('mode-guest-conference');
class Blink extends Component {
constructor() {
super();
autoBind(this)
this._initialSstate = {
accountId: '',
password: '',
displayName: '',
account: null,
registrationState: null,
currentCall: null,
connection: null,
inboundCall: null,
showIncomingModal: false,
showScreenSharingModal: false,
status: null,
targetUri: '',
missedTargetUri: '',
loading: null,
mode: MODE_PRIVATE,
localMedia: null,
generatedVideoTrack: false,
history: [],
serverHistory: [],
devices: {}
};
this.state = Object.assign({}, this._initialSstate);
this.__notificationCenter = null;
this.participantsToInvite = null;
this.redirectTo = null;
this.prevPath = null;
this.shouldUseHashRouting = false;
this.muteIncoming = false;
storage.initialize();
// Load camera/mic preferences
storage.get('devices').then((devices) => {
if (devices) {
this.setState({devices: devices});
}
});
}
get _notificationCenter() {
// getter to lazy-load the NotificationCenter ref
if (!this.__notificationCenter) {
this.__notificationCenter = this.refs.notificationCenter;
}
return this.__notificationCenter;
}
componentDidMount() {
history.push('/login');
// prime the ref
DEBUG('NotificationCenter ref: %o', this._notificationCenter);
}
connectionStateChanged(oldState, newState) {
DEBUG(`Connection state changed! ${oldState} -> ${newState}`);
switch (newState) {
case 'closed':
this.setState({connection: null, loading: null});
break;
case 'ready':
this.processRegistration(this.state.accountId, this.state.password, this.state.displayName);
break;
case 'disconnected':
// this.refs.audioPlayerOutbound.stop();
// this.refs.audioPlayerInbound.stop();
if (this.state.localMedia) {
sylkrtc.utils.closeMediaStream(this.state.localMedia);
}
if (this.state.currentCall) {
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
this.state.currentCall.terminate();
}
if (this.state.inboundCall && this.state.inboundCall !== this.state.currentCall) {
this.state.inboundCall.removeListener('stateChanged', this.inboundCallStateChanged);
this.state.inboundCall.terminate();
}
this.setState({
account:null,
registrationState: null,
loading: 'Disconnected, reconnecting...',
showIncomingModal: false,
currentCall: null,
inboundCall: null,
localMedia: null,
generatedVideoTrack: false
});
break;
default:
this.setState({loading: 'Connecting...'});
break;
}
}
notificationCenter() {
return this._notificationCenter;
}
registrationStateChanged(oldState, newState, data) {
DEBUG('Registration state changed! ' + newState);
this.setState({registrationState: newState});
if (newState === 'failed') {
let reason = data.reason;
if (reason.match(/904/)) {
// Sofia SIP: WAT
reason = 'Bad account or password';
} else {
reason = 'Connection failed';
}
this.setState({
loading : null,
status : {
msg : 'Sign In failed: ' + reason,
level : 'danger'
}
});
} else if (newState === 'registered') {
this.setState({loading: null});
console.log('pushing ready onto history');
history.push('/ready');
console.log('pushed ready onto history');
return;
} else {
this.setState({status: null });
}
}
callStateChanged(oldState, newState, data) {
DEBUG(`Call state changed! ${oldState} -> ${newState}`);
switch (newState) {
case 'progress':
//this.refs.audioPlayerOutbound.play(true);
break;
case 'accepted':
//this.refs.audioPlayerOutbound.stop();
//this.refs.audioPlayerInbound.stop();
break;
case 'terminated':
//this.refs.audioPlayerOutbound.stop();
//this.refs.audioPlayerInbound.stop();
//this.refs.audioPlayerHangup.play();
let callSuccesfull = false;
let reason = data.reason;
if (!reason || reason.match(/200/)) {
reason = 'Hangup';
callSuccesfull = true;
} else if (reason.match(/403/)) {
reason = 'This domain is not served here';
} else if (reason.match(/404/)) {
reason = 'User not found';
} else if (reason.match(/408/)) {
reason = 'Timeout';
} else if (reason.match(/480/)) {
reason = 'User not online';
} else if (reason.match(/486/) || reason.match(/60[036]/)) {
reason = 'Busy';
} else if (reason.match(/487/)) {
reason = 'Cancelled';
} else if (reason.match(/488/)) {
reason = 'Unacceptable media';
} else if (reason.match(/5\d\d/)) {
reason = 'Server failure';
} else if (reason.match(/904/)) {
// Sofia SIP: WAT
reason = 'Bad account or password';
} else {
reason = 'Connection failed';
}
this._notificationCenter.postSystemNotification('Call Terminated', {body: reason, timeout: callSuccesfull ? 5 : 10});
this.setState({
currentCall : null,
targetUri : callSuccesfull || config.useServerCallHistory ? '' : this.state.targetUri,
showIncomingModal : false,
inboundCall : null,
localMedia : null,
generatedVideoTrack : false
});
this.setFocusEvents(false);
this.participantsToInvite = null;
history.push('/ready');
break;
default:
break;
}
}
inboundCallStateChanged(oldState, newState, data) {
DEBUG('Inbound Call state changed! ' + newState);
if (newState === 'terminated') {
this.setState({ inboundCall: null, showIncomingModal: false });
this.setFocusEvents(false);
}
}
handleCallByUri(displayName, targetUri) {
const accountId = `${utils.generateUniqueId()}@${config.defaultGuestDomain}`;
this.setState({
accountId : accountId,
password : '',
displayName : displayName,
//mode : MODE_GUEST_CALL,
targetUri : utils.normalizeUri(targetUri, config.defaultDomain),
loading : 'Connecting...'
});
if (this.state.connection === null) {
let connection = sylkrtc.createConnection({server: config.wsServer});
connection.on('stateChanged', this.connectionStateChanged);
this.setState({connection: connection});
} else {
DEBUG('Connection Present, try to register');
this.processRegistration(accountId, '', displayName);
}
}
handleConferenceByUri(displayName, targetUri) {
const accountId = `${utils.generateUniqueId()}@${config.defaultGuestDomain}`;
this.setState({
accountId : accountId,
password : '',
displayName : displayName,
//mode : MODE_GUEST_CONFERENCE,
targetUri : targetUri,
loading : 'Connecting...'
});
if (this.state.connection === null) {
let connection = sylkrtc.createConnection({server: config.wsServer});
connection.on('stateChanged', this.connectionStateChanged);
this.setState({connection: connection});
} else {
DEBUG('Connection Present, try to register');
this.processRegistration(accountId, '', displayName);
}
}
handleRegistration(accountId, password, remember) {
this.setState({
accountId : accountId,
password : password,
mode : remember ? MODE_NORMAL : MODE_PRIVATE,
loading : 'Connecting...'
});
if (this.state.connection === null) {
let connection = sylkrtc.createConnection({server: config.wsServer});
connection.on('stateChanged', this.connectionStateChanged);
this.setState({connection: connection});
console.log('HALP');
} else {
DEBUG('Connection Present, try to register');
this.processRegistration(accountId, password, '');
}
}
processRegistration(accountId, password, displayName) {
if (this.state.account !== null) {
DEBUG('We already have an account, removing it');
this.state.connection.removeAccount(this.state.account,
(error) => {
if (error) {
DEBUG(error);
}
this.setState({account: null, registrationState: null});
}
);
}
const options = {
account: accountId,
password: password,
displayName: displayName
};
const account = this.state.connection.addAccount(options, (error, account) => {
if (!error) {
account.on('outgoingCall', this.outgoingCall);
account.on('conferenceCall', this.outgoingCall);
switch (this.state.mode) {
case MODE_PRIVATE:
case MODE_NORMAL:
account.on('registrationStateChanged', this.registrationStateChanged);
account.on('incomingCall', this.incomingCall);
account.on('missedCall', this.missedCall);
account.on('conferenceInvite', this.conferenceInvite);
this.setState({account: account});
this.state.account.register();
if (this.state.mode !== MODE_PRIVATE) {
storage.set('account', {
accountId: this.state.accountId,
password: this.state.password
});
} else {
// Wipe storage if private login
//storage.remove('account'); // lets try this out
// history.clear().then(() => {
// this.setState({history: []});
// });
}
break;
case MODE_GUEST_CALL:
this.setState({account: account, loading: null, registrationState: 'registered'});
DEBUG(`${accountId} (guest) signed in`);
// Start the call immediately, this is call started with "Call by URI"
this.startGuestCall(this.state.targetUri, {audio: true, video: true});
break;
case MODE_GUEST_CONFERENCE:
this.setState({account: account, loading: null, registrationState: 'registered'});
DEBUG(`${accountId} (conference guest) signed in`);
// Start the call immediately, this is call started with "Conference by URI"
this.startGuestConference(this.state.targetUri);
break;
default:
DEBUG(`Unknown mode: ${this.state.mode}`);
break;
}
} else {
DEBUG('Add account error: ' + error);
this.setState({loading: null, status: {msg: error.message, level:'danger'}});
}
});
}
setDevice(device) {
const oldDevices = Object.assign({}, this.state.devices);
if (device.kind === 'videoinput') {
oldDevices['camera'] = device;
} else if (device.kind === 'audioinput') {
oldDevices['mic'] = device;
}
this.setState({devices: oldDevices});
storage.set('devices', oldDevices);
sylkrtc.utils.closeMediaStream(this.state.localMedia);
this.getLocalMedia();
}
getLocalMedia(mediaConstraints={audio: true, video: true}, nextRoute=null) { // eslint-disable-line space-infix-ops
DEBUG('getLocalMedia(), mediaConstraints=%o', mediaConstraints);
const constraints = Object.assign({}, mediaConstraints);
if (constraints.video === true) {
if ((nextRoute === '/conference' || this.state.mode === MODE_GUEST_CONFERENCE)) {
constraints.video = {
'width': {
'ideal': 640
},
'height': {
'ideal': 480
}
};
// TODO: remove this, workaround so at least safari works wehn joining a video conference
} else if ((nextRoute === '/conference' || this.state.mode === MODE_GUEST_CONFERENCE) && isSafari) {
constraints.video = false;
} else {
// ask for 720p video
constraints.video = {
'width': {
'ideal': 1280
},
'height': {
'ideal': 720
}
};
}
}
DEBUG('getLocalMedia(), (modified) mediaConstraints=%o', constraints);
this.loadScreenTimer = setTimeout(() => {
this.setState({loading: 'Please allow access to your media devices'});
}, 150);
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
if ('video' in constraints && 'camera' in this.state.devices) {
if (constraints.video !== false && (device.deviceId === this.state.devices.camera.deviceId || device.label === this.state.devices.camera.label)) {
constraints.video.deviceId = {
exact: device.deviceId
};
}
}
if ('mic' in this.state.devices) {
if (device.deviceId === this.state.devices.mic.deviceId || device.label === this.state.devices.mic.Label) {
constraints.audio = {
deviceId: {
exact: device.deviceId
}
};
}
}
});
})
.catch((error) => {
DEBUG('Device enumeration failed: %o', error);
})
.then(() => {
return navigator.mediaDevices.getUserMedia(constraints)
})
.then((localStream) => {
clearTimeout(this.loadScreenTimer);
DEBUG('Got local Media', localStream);
this.setState({status: null, loading: null, localMedia: localStream});
if (nextRoute !== null) {
history.push(nextRoute);
}
})
.catch((error) => {
DEBUG('Access failed, trying audio only: %o', error);
navigator.mediaDevices.getUserMedia({
audio: true,
video: false
})
.then((localStream) => {
clearTimeout(this.loadScreenTimer);
if (nextRoute != '/preview') {
DEBUG('Audio only media, but video was requested, creating generated video track');
const generatedVideoTrack = utils.generateVideoTrack(localStream);
localStream.addTrack(generatedVideoTrack);
}
this.setState({status: null, loading: null, localMedia: localStream, generatedVideoTrack: true});
if (nextRoute !== null) {
history.push(nextRoute);
}
})
.catch((error) => {
DEBUG('Access to local media failed: %o', error);
clearTimeout(this.loadScreenTimer);
this._notificationCenter.postSystemNotification("Can't access camera or microphone", {timeout: 10});
this.setState({
loading: null
});
});
});
}
startCall(targetUri, options) {
this.setState({targetUri: targetUri});
this.addCallHistoryEntry(targetUri);
this.getLocalMedia(Object.assign({audio: true, video: true}, options), '/call');
}
startGuestCall(targetUri, options) {
this.setState({targetUri: targetUri});
this.getLocalMedia(Object.assign({audio: true, video: true}, options));
}
answerCall(options) {
this.setState({ showIncomingModal: false });
this.setFocusEvents(false);
if (this.state.inboundCall !== this.state.currentCall) {
// terminate current call to switch to incoming one
this.state.inboundCall.removeListener('stateChanged', this.inboundCallStateChanged);
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
this.state.currentCall.terminate();
this.setState({currentCall: this.state.inboundCall, inboundCall: this.state.inboundCall, localMedia: null});
this.state.inboundCall.on('stateChanged', this.callStateChanged);
}
this.getLocalMedia(Object.assign({audio: true, video: true}, options), '/call');
}
rejectCall() {
this.setState({showIncomingModal: false});
this.state.inboundCall.terminate();
}
hangupCall() {
if (this.state.currentCall != null) {
this.state.currentCall.terminate();
} else {
// We have no call but we still want to cancel
if (this.state.localMedia != null) {
sylkrtc.utils.closeMediaStream(this.state.localMedia);
}
history.push('/ready');
}
}
escalateToConference(participants) {
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
this.state.currentCall.terminate();
history.push('/ready');
this.setState({currentCall: null, localMedia: null});
this.participantsToInvite = participants;
const uri = `${utils.generateSillyName()}@${config.defaultConferenceDomain}`;
this.startConference(uri);
}
startConference(targetUri) {
this.setState({targetUri: targetUri});
this.getLocalMedia({audio: true, video: true}, '/conference');
}
startGuestConference(targetUri) {
this.setState({targetUri: targetUri});
this.getLocalMedia({audio: true, video: true});
}
toggleMute() {
this.muteIncoming = !this.muteIncoming;
}
outgoingCall(call) {
call.on('stateChanged', this.callStateChanged);
this.setState({currentCall: call});
}
incomingCall(call, mediaTypes) {
DEBUG('New incoming call from %o with %o', call.remoteIdentity, mediaTypes);
if (!mediaTypes.audio && !mediaTypes.video) {
call.terminate();
return;
}
call.mediaTypes = mediaTypes;
if (this.state.currentCall !== null) {
// detect if we called ourselves
if (this.state.currentCall.localIdentity.uri === call.remoteIdentity.uri) {
DEBUG('Aborting call to myself');
call.terminate();
return;
}
this.setState({ showIncomingModal: true, inboundCall: call });
this.setFocusEvents(true);
call.on('stateChanged', this.inboundCallStateChanged);
} else {
if (!this.muteIncoming) {
//this.refs.audioPlayerInbound.play(true);
}
this.setFocusEvents(true);
call.on('stateChanged', this.callStateChanged);
this.setState({currentCall: call, inboundCall: call, showIncomingModal: true});
}
// if (!this.shouldUseHashRouting) {
// this._notificationCenter.postSystemNotification('Incoming call', {body: `From ${call.remoteIdentity.displayName || call.remoteIdentity.uri}`, timeout: 15, silent: false});
// }
}
setFocusEvents(enabled) {
// if (this.shouldUseHashRouting) {
// const remote = window.require('electron').remote;
// if (enabled) {
// const currentWindow = remote.getCurrentWindow();
// currentWindow.on('focus', this.hasFocus);
// currentWindow.on('blur', this.hasNoFocus);
// this.setState({haveFocus: currentWindow.isFocused()});
// } else {
// const currentWindow = remote.getCurrentWindow();
// currentWindow.removeListener('focus', this.hasFocus);
// currentWindow.removeListener('blur', this.hasNoFocus);
// }
// }
}
// hasFocus() {
// this.setState({haveFocus: true});
// }
// hasNoFocus() {
// this.setState({haveFocus: false});
// }
missedCall(data) {
DEBUG('Missed call from ' + data.originator);
this._notificationCenter.postSystemNotification('Missed call', {body: `From ${data.originator.displayName || data.originator.uri}`, timeout: 15, silent: false});
if (this.state.currentCall !== null || !config.useServerCallHistory) {
this._notificationCenter.postMissedCall(data.originator, () => {
if (this.state.currentCall !== null) {
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
this.state.currentCall.terminate();
this.setState({currentCall: null, missedTargetUri: data.originator.uri, showIncomingModal: false, localMedia: null});
} else {
this.setState({missedTargetUri: data.originator.uri});
}
history.push('/ready');
});
} else {
this.getServerHistory();
}
}
conferenceInvite(data) {
DEBUG('Conference invite from %o to %s', data.originator, data.room);
this._notificationCenter.postSystemNotification('Conference invite', {body: `From ${data.originator.displayName || data.originator.uri} for room ${data.room}`, timeout: 15, silent: false});
this._notificationCenter.postConferenceInvite(data.originator, data.room, () => {
if (this.state.currentCall !== null) {
this.state.currentCall.removeListener('stateChanged', this.callStateChanged);
this.state.currentCall.terminate();
this.setState({currentCall: null, showIncomingModal: false, localMedia: null, generatedVideoTrack: false});
}
setTimeout(() => {
this.startConference(data.room);
});
});
}
startPreview() {
this.getLocalMedia({audio: true, video: true}, '/preview');
}
addCallHistoryEntry(uri) {
if (this.state.mode === MODE_NORMAL) {
// history.add(uri).then((entries) => {
// this.setState({history: entries});
// });
} else {
let entries = this.state.history.slice();
if (entries.length !== 0) {
const idx = entries.indexOf(uri);
if (idx !== -1) {
entries.splice(idx, 1);
}
entries.unshift(uri);
// keep just the last 50
entries = entries.slice(0, 50);
} else {
entries = [uri];
}
this.setState({history: entries});
}
}
getServerHistory() {
if (!config.useServerCallHistory) {
return;
}
DEBUG('Requesting call history from server');
let getServerCallHistory = new DigestAuthRequest(
'GET',
`${config.serverCallHistoryUrl}?action=get_history&realm=${this.state.account.id.split('@')[1]}`,
this.state.account.id.split('@')[0],
this.state.password
);
// Disable logging
getServerCallHistory.loggingOn = false;
getServerCallHistory.request((data) => {
if (data.success !== undefined && data.success === false) {
DEBUG('Error getting call history from server: %o', data.error_message)
return;
}
let history = []
data.placed.map(elem => {elem.direction = 'placed'; return elem});
data.received.map(elem => {elem.direction = 'received'; return elem});
history = data.placed;
history = history.concat(data.received);
history.sort((a,b) => {
return new Date(b.startTime) - new Date(a.startTime);
});
const known = [];
history = history.filter((elem) => {
if (known.indexOf(elem.remoteParty) <= -1) {
if ((elem.media.indexOf('audio') > -1 || elem.media.indexOf('video') > -1) &&
(elem.remoteParty !== this.state.account.id || elem.direction !== 'placed')) {
known.push(elem.remoteParty);
return elem;
}
}
});
this.setState({serverHistory: history});
}, (errorCode) => {
DEBUG('Error getting call history from server: %o', errorCode)
});
}
// checkRoute(nextPath, navigation, match) {
// if (nextPath !== this.prevPath) {
// DEBUG(`Transition from ${this.prevPath} to ${nextPath}`);
// if (config.useServerCallHistory && nextPath === '/ready' && this.state.registrationState === 'registered' && (this.state.mode !== MODE_GUEST_CALL && this.state.mode !== MODE_GUEST_CONFERENCE)) {
// this.getServerHistory();
// }
// // Press back in ready after a login, prevent initial navigation
// // don't deny if there is no registrationState (connection fail)
// if (this.prevPath === '/ready' && nextPath === '/login' && this.state.registrationState !== null) {
// DEBUG('Transition denied redirecting to /logout');
// history.push('/logout');
// return false;
// // Press back in ready after a call
// } else if ((nextPath === '/call' || nextPath === '/conference') && this.state.localMedia === null && this.state.registrationState === 'registered') {
// return false;
// // Press back from within a call/conference, don't navigate terminate the call and
// // let termination take care of navigating
// } else if (nextPath === '/ready' && this.state.registrationState === 'registered' && this.state.currentCall !== null) {
// this.state.currentCall.terminate();
// return false;
// // Guest call ended, needed to logout and display msg and logout
// } else if (nextPath === '/ready' && (this.state.mode === MODE_GUEST_CALL || this.state.mode === MODE_GUEST_CONFERENCE)) {
// history.push('/logout');
// this.forceUpdate();
// }
// }
// this.prevPath = nextPath;
// }
render() {
let footerBox = ;
let extraStyles = {};
if (this.state.localMedia || this.state.registrationState === 'registered') {
footerBox = null;
}
return (
{/* */}
{/* */}
{/* */}
{footerBox}
);
}
notFound(match) {
const status = {
title : '404',
message : 'Oops, the page your looking for can\'t found',
level : 'danger',
width : 'large'
}
return (
);
}
ready() {
if (this.state.registrationState !== 'registered') {
setTimeout(() => {
history.push('/login');
});
return false;
};
return (
-
+
-
+
);
}
preview() {
if (this.state.registrationState !== 'registered') {
setTimeout(() => {
history.push('/login');
});
return false;
};
return (
-
+
-
+
);
}
call() {
if (this.state.registrationState !== 'registered') {
setTimeout(() => {
history.push('/login');
});
return false;
};
return (
)
}
callByUri(urlParameters) {
// check if the uri contains a domain
if (urlParameters.targetUri.indexOf('@') === -1) {
const status = {
title : 'Invalid user',
message : `Oops, the domain of the user is not set in '${urlParameters.targetUri}'`,
level : 'danger',
width : 'large'
}
return (
);
}
return (
);
}
conference() {
if (this.state.registrationState !== 'registered') {
setTimeout(() => {
history.push('/login');
});
return false;
};
return (
)
}
conferenceByUri(urlParameters) {
const targetUri = utils.normalizeUri(urlParameters.targetUri, config.defaultConferenceDomain);
const idx = targetUri.indexOf('@');
const uri = {};
const pattern = /^[A-Za-z0-9\-\_]+$/g;
uri.user = targetUri.substring(0, idx);
// check if the uri.user is valid
if (!pattern.test(uri.user)) {
const status = {
title : 'Invalid conference',
message : `Oops, the conference ID is invalid: ${targetUri}`,
level : 'danger',
width : 'large'
}
return (
);
}
return (
);
}
login() {
let registerBox;
let statusBox;
if (this.state.status !== null) {
statusBox = (
);
}
if (this.state.registrationState !== 'registered') {
registerBox = (
);
}
return (
{registerBox}
{statusBox}
);
}
logout() {
setTimeout(() => {
if (this.state.registrationState !== null && (this.state.mode === MODE_NORMAL || this.state.mode === MODE_PRIVATE)) {
this.state.account.unregister();
}
if (this.state.account !== null) {
this.state.connection.removeAccount(this.state.account,
(error) => {
if (error) {
DEBUG(error);
}
}
);
}
storage.set('account', {accountId: this.state.accountId, password: ''});
this.setState({account: null, registrationState: null, status: null});
history.push('/login');
});
return ;
}
main() {
return (
);
}
}
export default Blink;
\ No newline at end of file
diff --git a/app/assets/styles/blink/_Preview.scss b/app/assets/styles/blink/_Preview.scss
index c4f6710..e3fff9c 100644
--- a/app/assets/styles/blink/_Preview.scss
+++ b/app/assets/styles/blink/_Preview.scss
@@ -1,24 +1,32 @@
.container {
flex: 1;
+ height: 100%;
+ width: 100%;
+ display: flex;
}
.video {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
width: 100%;
height: 100%;
}
.buttonContainer {
position: absolute;
bottom: 100;
margin: 0 auto;
width: 100%;
z-index: 99;
justify-content: center;
align-items: center;
}
.button {
color: white;
background-color: rgba(#a94442, .8);
}
diff --git a/app/components/DTMFModal.js b/app/components/DTMFModal.js
index cc6c84a..bdabc5c 100644
--- a/app/components/DTMFModal.js
+++ b/app/components/DTMFModal.js
@@ -1,67 +1,67 @@
import debug from 'debug';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { Portal, Modal, Surface, Title, Button } from 'react-native-paper';
import dtmf from 'react-native-dtmf';
import styles from '../assets/styles/blink/_DTMFModal.scss';
const DEBUG = debug('blinkrtc:DTMF');
debug.enable('*');
class DTMFModal extends Component {
sendDtmf(tone) {
DEBUG('DTMF tone was sent: ' + tone);
dtmf.stopTone();//don't play a tone at the same time as another
- dtmf.playTone(dtmf['DTMF_' + tone], 1000);
+ dtmf.playTone(dtmf['DTMF_' + tone], 500);
if (this.props.call !== null && this.props.call.state === 'established') {
this.props.call.sendDtmf(tone);
}
}
render() {
return (
DTMF
);
}
}
DTMFModal.propTypes = {
show: PropTypes.bool.isRequired,
hide: PropTypes.func.isRequired,
call: PropTypes.object
};
export default DTMFModal;
diff --git a/app/components/NotificationCenter.js b/app/components/NotificationCenter.js
index 6f1643d..ece8244 100644
--- a/app/components/NotificationCenter.js
+++ b/app/components/NotificationCenter.js
@@ -1,158 +1,158 @@
import React, { Component } from 'react';
import { ProgressBar, Colors, Snackbar } from 'react-native-paper';
import moment from 'moment';
import autoBind from 'auto-bind';
import config from '../config';
class NotificationCenter extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
visible: false,
message: null,
title: null,
autoDismiss: null,
action: null
}
}
postSystemNotification(title, options={}) { // eslint-disable-line space-infix-ops
this.setState({
visible: true,
- autoDismiss: 3,
+ autoDismiss: 6,
title: title,
message: options.body
});
}
postConferenceInvite(originator, room, cb) {
if (originator.uri.endsWith(config.defaultGuestDomain)) {
return;
}
const idx = room.indexOf('@');
if (idx === -1) {
return;
}
const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss');
const action = {
label: 'Join',
onPress: () => { cb(room); }
};
this.setState({
visible: true,
message: `${(originator.displayName || originator.uri)} invited you to join conference room ${room.substring(0, idx)}
On ${currentDate}`,
title: 'Conference Invite',
autoDismiss: 0,
action: action,
});
}
postMissedCall(originator, cb) {
const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss');
let action;
if (originator.uri.endsWith(config.defaultGuestDomain)) {
action = null;
} else {
action = {
label: 'Call',
onPress: () => { cb(originator.uri); }
};
}
this.setState({
visible: true,
message: `From ${(originator.displayName || originator.uri)}
On ${currentDate}`,
title: 'Missed Call',
autoDismiss: 0,
action: action
});
}
postFileUploadProgress(filename, cb) {
this.setState({
visible: true,
message: `${filename}`,
title: 'Uploading file',
autoDismiss: 0,
action: {
label: 'OK',
onPress: () => cb()
},
// children: (
//
//
//
// )
});
}
editFileUploadNotification(progress, notification) {
if (progress === undefined) {
progress = 100;
}
this.setState({
visible: true,
message: `${filename}`,
title: 'Upload Successful',
autoDismiss: 3,
});
}
removeFileUploadNotification(notification) {
let timer = setTimeout(() => {
this.setState({visible: false});
}, 3000);
}
removeNotification(notification) {
this.setState({visible: false});
}
postFileUploadFailed(filename) {
this.setState({
visible: true,
message: `Uploading of ${filename} failed`,
title: 'File sharing failed',
autoDismiss: 10,
});
}
postFileShared(file, cb) {
const uploader = file.uploader.displayName || file.uploader.uri || file.uploader;
this.setState({
visible: true,
message: `${uploader} shared ${file.filename}`,
title: 'File shared',
autoDismiss: 10,
action: {
label: 'Show Files',
onPress: () => cb()
}
});
}
render() {
console.log('Re-rendering snackbar');
return (
this.setState({ visible: false })}
action={this.state.action}
>
{this.state.title} - {this.state.message}
);
}
}
export default NotificationCenter;
\ No newline at end of file
diff --git a/app/components/Preview.js b/app/components/Preview.js
index 90703d5..161e533 100644
--- a/app/components/Preview.js
+++ b/app/components/Preview.js
@@ -1,196 +1,195 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import debug from 'react-native-debug';
import { View } from 'react-native';
import { Text, IconButton, List, Appbar } from 'react-native-paper';
import autoBind from 'auto-bind';
import { RTCView } from 'react-native-webrtc';
import ConferenceDrawer from './ConferenceDrawer';
import VolumeBar from './VolumeBar';
import styles from '../assets/styles/blink/_Preview.scss';
const DEBUG = debug('blinkrtc:Preview');
debug.enable('*');
class Preview extends Component {
constructor(props) {
super(props);
autoBind(this);
let mic = { label: 'No mic' };
let camera = { label: 'No Camera' };
if ('camera' in this.props.selectedDevices) {
camera = this.props.selectedDevices.camera;
} else if (this.props.localMedia.getVideoTracks().length !== 0) {
camera.label = this.props.localMedia.getVideoTracks()[0].label;
}
if ('mic' in this.props.selectedDevices) {
mic = this.props.selectedDevices.mic;
} else if (this.props.localMedia.getAudioTracks().length !== 0) {
mic.label = this.props.localMedia.getAudioTracks()[0].label;
}
this.state = {
camera: camera,
showDrawer: false,
mic: mic,
streamURL: null
}
this.devices = [];
this.localVideo = React.createRef();
}
componentDidMount() {
console.log( this.props.localMedia);
this.setState({streamURL: this.props.localMedia});
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
this.devices = devices;
console.log(devices);
let newState = {};
if (this.state.camera.label !== 'No Camera') {
if (!devices.find((device) => {return device.kind === 'videoinput'})) {
newState.camera = {label: 'No Camera'};
} else if (this.props.localMedia.getVideoTracks().length !== 0) {
newState.camera = {label: this.props.localMedia.getVideoTracks()[0].label};
}
}
if (this.state.mic.label !== 'No mic') {
if (!devices.find((device) => {return device.kind === 'audioinput'})) {
newState.mic = {label: 'No mic'};
} else if (this.props.localMedia.getAudioTracks().length !== 0) {
newState.mic = { label: this.props.localMedia.getAudioTracks()[0].label};
}
}
if (Object.keys(newState).length != 0) {
this.setState(Object.assign({},newState));
}
})
.catch(function(error) {
DEBUG('Device enumeration failed: %o', error);
});
}
componentWillReceiveProps(nextProps) {
if (nextProps.localMedia !== this.props.localMedia) {
this.setState({streamURL: nextProps.localMedia})
}
if (nextProps.selectedDevices !== this.props.selectedDevices) {
let camera = {label: 'No Camera'};
let mic = {label: 'No Mic'};
if ('camera' in nextProps.selectedDevices) {
camera = nextProps.selectedDevices.camera;
}
if ('mic' in nextProps.selectedDevices) {
mic = nextProps.selectedDevices.mic;
}
this.setState({ camera, mic });
}
}
setDevice = (device) => (e) => {
e.preventDefault();
if (device.label !== this.state.mic.label && device.label !== this.state.camera.label) {
this.props.setDevice(device);
+ this.setState({showDrawer: false});
}
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall();
}
toggleDrawer() {
this.setState({showDrawer: !this.state.showDrawer});
}
render() {
let cameras = [];
let mics = [];
this.devices.forEach((device) => {
if (device.kind === 'videoinput') {
cameras.push(
-
+
);
} else if (device.kind === 'audioinput') {
mics.push(
-
+
);
}
});
let header = null;
if (this.state.camera !== '') {
header = (
-
+
{ !this.state.showDrawer ?
: null }
-
+
);
}
let drawercontent = (
Video Camera
{cameras}
Audio Input
{mics}
);
console.log(this.state.streamURL);
return (
-
+
{header}
+
+
+
- { this.state.streamURL ?
-
- : null }
-
-
-
+
{drawercontent}
);
}
}
Preview.propTypes = {
hangupCall: PropTypes.func,
localMedia: PropTypes.object.isRequired,
setDevice: PropTypes.func.isRequired,
selectedDevices: PropTypes.object.isRequired
};
export default Preview;