diff --git a/android/gradle.properties b/android/gradle.properties
index 4cff491..ccb748f 100644
Binary files a/android/gradle.properties and b/android/gradle.properties differ
diff --git a/app/app.js b/app/app.js
index 84acfd1..9687dda 100644
--- a/app/app.js
+++ b/app/app.js
@@ -1,1062 +1,1060 @@
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 ;
+ return ;
}
main() {
- return (
-
- );
+ return null;
}
}
export default Blink;
\ No newline at end of file
diff --git a/app/assets/styles/blink/_AudioCallBox.scss b/app/assets/styles/blink/_AudioCallBox.scss
index c9a4ac1..d72c7db 100644
--- a/app/assets/styles/blink/_AudioCallBox.scss
+++ b/app/assets/styles/blink/_AudioCallBox.scss
@@ -1,18 +1,26 @@
.container {
}
.buttonContainer {
flex-direction: row;
margin: 0 auto;
padding-top: 10px;
}
.button {
background-color: white;
margin: 10px;
}
.hangupButton {
background-color: rgba(#a94442, .8);
+}
+
+.userIconContainer {
+
+}
+
+.userIcon {
+ margin: 0 auto;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_CallMeMaybeModal.scss b/app/assets/styles/blink/_CallMeMaybeModal.scss
index d1672bc..95ad425 100644
--- a/app/assets/styles/blink/_CallMeMaybeModal.scss
+++ b/app/assets/styles/blink/_CallMeMaybeModal.scss
@@ -1,4 +1,8 @@
.container {
padding: 30px;
margin: 10px;
+}
+
+.iconContainer {
+ flex-direction: row;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceBox.scss b/app/assets/styles/blink/_ConferenceBox.scss
index c93b13c..f0a4d2c 100644
--- a/app/assets/styles/blink/_ConferenceBox.scss
+++ b/app/assets/styles/blink/_ConferenceBox.scss
@@ -1,25 +1,42 @@
+.container {
+ flex: 1;
+}
+
.button {
background-color: white;
margin: 10px;
}
-.container {
- flex: 1;
- height: 100%;
+.conferenceContainer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+ align-content: flex-end;
}
-.videoContainer {
+.videosContainer {
flex: 1;
- display: flex;
- background-color: pink;
}
.hangupButton {
background-color: rgba(#a94442, .8);
}
.wholePageVideo {
width: 100%;
height: 100%;
- border: 1px orange solid;
+}
+
+.buttonContainer {
+ flex-direction: row;
+ margin: 0 auto;
+}
+
+.carouselContainer {
+ position: absolute;
+ bottom: 10;
+ left: 0;
+ right: 0;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceCarousel.scss b/app/assets/styles/blink/_ConferenceCarousel.scss
index c452f93..595bbbf 100644
--- a/app/assets/styles/blink/_ConferenceCarousel.scss
+++ b/app/assets/styles/blink/_ConferenceCarousel.scss
@@ -1,5 +1,3 @@
.container {
- position: absolute;
- width: 100%;
- bottom: 0;
+
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceHeader.scss b/app/assets/styles/blink/_ConferenceHeader.scss
index 1cda230..cba1a26 100644
--- a/app/assets/styles/blink/_ConferenceHeader.scss
+++ b/app/assets/styles/blink/_ConferenceHeader.scss
@@ -1,10 +1,10 @@
.container {
flex: 1;
+ z-index: 10000;
}
.buttonContainer {
flex: 1;
flex-direction: row;
margin: 0 auto;
- padding-top: 10px;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceMatrixParticipant.scss b/app/assets/styles/blink/_ConferenceMatrixParticipant.scss
index 1a4adea..b83a4e5 100644
--- a/app/assets/styles/blink/_ConferenceMatrixParticipant.scss
+++ b/app/assets/styles/blink/_ConferenceMatrixParticipant.scss
@@ -1,20 +1,21 @@
.container {
flex: 1;
}
-.videoContainer {
- flex: 1;
+.soloContainer {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
}
-.videoLarge {
+.videoContainer {
+ height: 100%;
width: 100%;
- min-height: 100%;
- border: 1px green solid;
}
.video {
- margin: 0 auto;
- width: 90%;
- height: 500px;
- border: 1px yellow solid;
+ height: 100%;
+ width: 100%;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceParticipant.scss b/app/assets/styles/blink/_ConferenceParticipant.scss
new file mode 100644
index 0000000..29ca1f4
--- /dev/null
+++ b/app/assets/styles/blink/_ConferenceParticipant.scss
@@ -0,0 +1,11 @@
+.container {
+ flex: 1;
+}
+
+.videoContainer {
+
+}
+
+.video {
+
+}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_ConferenceParticipantSelf.scss b/app/assets/styles/blink/_ConferenceParticipantSelf.scss
index c58fbad..9dc438e 100644
--- a/app/assets/styles/blink/_ConferenceParticipantSelf.scss
+++ b/app/assets/styles/blink/_ConferenceParticipantSelf.scss
@@ -1,9 +1,8 @@
.container {
flex: 1;
}
.video {
height: 100px;
width: 100px;
- border: 1px purple solid;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_Footer.scss b/app/assets/styles/blink/_Footer.scss
index bf76cb9..34db4cf 100644
--- a/app/assets/styles/blink/_Footer.scss
+++ b/app/assets/styles/blink/_Footer.scss
@@ -1,8 +1,7 @@
.container {
margin: 0 auto;
- padding-bottom: 15px;
}
.text {
color: white;
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_LocalMedia.scss b/app/assets/styles/blink/_LocalMedia.scss
index d8029a9..5ee57de 100644
--- a/app/assets/styles/blink/_LocalMedia.scss
+++ b/app/assets/styles/blink/_LocalMedia.scss
@@ -1,23 +1,22 @@
.container {
flex: 1;
height: 100%;
width: 100%;
- display: flex;
}
.video {
}
.buttonContainer {
position: absolute;
bottom: 100;
width: 100%;
z-index: 99;
justify-content: center;
align-items: center;
}
.button {
background-color: rgba(#a94442, .8);
}
diff --git a/app/assets/styles/blink/_NavigationBar.scss b/app/assets/styles/blink/_NavigationBar-old.scss
similarity index 100%
copy from app/assets/styles/blink/_NavigationBar.scss
copy to app/assets/styles/blink/_NavigationBar-old.scss
diff --git a/app/assets/styles/blink/_NavigationBar.scss b/app/assets/styles/blink/_NavigationBar.scss
index 635296b..57829bd 100644
--- a/app/assets/styles/blink/_NavigationBar.scss
+++ b/app/assets/styles/blink/_NavigationBar.scss
@@ -1,44 +1,12 @@
-// Values
-// 31 -> inner buttonsize + border
-// 38 -> default padding is 6 on a 50px 50-12
-// 20 -> default line-height
+.titleContainer {
-.navbar-header {
- float: left !important;
}
-.navbar-blink-logo {
- @include background-image-retina($navbar-logo-image, $navbar-height, $navbar-height);
- width: $navbar-height;
- height: $navbar-height;
- margin: 0 auto;
- margin-left: 15px;
+.title {
+ color: white;
}
-.navbar-btn-toolbar {
- margin-top: ($navbar-height - 31 - zero($navbar-height - 38)) / 2; // 40 -> 7/2, 50 -> 7/2
-}
-
-.navbar {
- min-height: $navbar-height;
-
- button {
- padding: zero(($navbar-height - 38) / 2) 12px;
-
- &.btn-fw {
- width: 58px;
- margin-right: -5px;
- }
- }
-}
-
-.navbar-brand {
- height: $navbar-height;
- padding: ($navbar-height - 20) / 2 15px;
- padding-left: 0;
- margin-left: 0 !important;
-}
-
-.navbar-text {
- margin: ($navbar-height - 20) / 2 15px;
-}
+.logo {
+ height: 50px;
+ width: 50px;
+}
\ No newline at end of file
diff --git a/app/assets/styles/blink/_Preview.scss b/app/assets/styles/blink/_Preview.scss
index e3fff9c..98cdebf 100644
--- a/app/assets/styles/blink/_Preview.scss
+++ b/app/assets/styles/blink/_Preview.scss
@@ -1,32 +1,29 @@
.container {
flex: 1;
- height: 100%;
+}
+
+.videoContainer {
width: 100%;
- display: flex;
+ height: 100%;
}
.video {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- width: 100%;
height: 100%;
+ width: 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/assets/styles/blink/_ReadyBox.scss b/app/assets/styles/blink/_ReadyBox.scss
index 996ee7e..9d65925 100644
--- a/app/assets/styles/blink/_ReadyBox.scss
+++ b/app/assets/styles/blink/_ReadyBox.scss
@@ -1,30 +1,36 @@
@import './variables';
+.wholeContainer {
+ flex: 1;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.container {
+ padding: 30px;
+}
+
.title {
color: white;
}
.footer {
- position: absolute;
- bottom: 0;
- font-size: 11px;
- color: $white-transparent;
- text-shadow: 0 1px 3px $black-transparent;
- width: 100%;
-}
-
-.container {
- padding: 30px;
+ flex: 1;
+ justify-content: flex-end;
+ padding-bottom: 15px;
}
.button {
background-color: grey;
margin: 10px;
}
.buttonGroup {
- flex: 1;
flex-direction: row;
margin: 0 auto;
padding-top: 10px;
}
+
+.uriInputBox {
+ padding-top: 10px;
+}
diff --git a/app/assets/styles/blink/_VideoBox.scss b/app/assets/styles/blink/_VideoBox.scss
index 9dc3407..bf5b945 100644
--- a/app/assets/styles/blink/_VideoBox.scss
+++ b/app/assets/styles/blink/_VideoBox.scss
@@ -1,44 +1,43 @@
.container {
flex: 1;
}
.remoteVideoContainer {
-
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
}
.localVideoContainer {
- flex: 1;
justify-content: flex-end;
}
.video {
-
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ background-color: red;
}
.localVideo {
height: 100px;
width: 100px;
- object-fit: fill;
+ object-fit: cover;
+ background-color: white;
}
.buttonContainer {
flex-direction: row;
margin: 0 auto;
- // position: absolute;
- // right: 0;
- // bottom: 0;
- // left: 0;
- // z-index: 1;
- // padding-bottom: 35px;
- // margin: auto;
- // overflow: hidden;
}
.button {
background-color: white;
margin: 10px;
}
.hangupButton {
background-color: rgba(#a94442, .8);
}
\ No newline at end of file
diff --git a/app/assets/styles/blink/root.scss b/app/assets/styles/blink/root.scss
index e74b745..fff55c1 100644
--- a/app/assets/styles/blink/root.scss
+++ b/app/assets/styles/blink/root.scss
@@ -1,107 +1,102 @@
@import './variables';
@import './mixins';
.root {
color: $base-foreground-color;
text-align: center;
position: absolute;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
flex: 1;
height: 100%;
left: 0;
right: 0;
bottom: 0;
top: 0;
}
// Padding for spacing
.inner {
padding: 30px;
}
.inner-small {
padding: 10px;
}
// Stckiy layer to top
.sticky-wrapper {
position: sticky;
top: -1px;
z-index: 1;
margin-top: calc(50vh - 159px);
margin-right: -20px;
margin-left: -20px;
}
.sticky {
//sass-lint:disable-block no-color-literals, property-sort-order
background-color: $black-less-transparent;
//background: linear-gradient(180deg, $black 0%, rgba($black, .95) 15%, rgba($black, .55) 80%, rgba($black, 0) 100%);
}
// Cover
.cover {
padding: 0 20px;
.btn-lg {
padding: 10px 20px;
font-weight: bold;
}
}
// Scroll main section
.scroll {
display: flex;
flex-direction: column;
height: calc(100vh - 50px);
margin-top: 50px;
overflow-x: hidden;
overflow-y: auto;
.footer {
position: static;
padding-top: 10px;
}
}
// Footer
.footer {
- position: absolute;
- bottom: 0;
- font-size: 11px;
- color: $white-transparent;
- text-shadow: 0 1px 3px $black-transparent;
- width: 100%;
+ flex: 1;
}
// Handle the widths
.cover-container,
.half-width {
width: 100%; // Must be percentage or pixels for horizontal alignment
}
@media (min-width: 992px) {
.half-width {
width: 500px;
margin: auto;
}
}
// Helper to include shadow if footer is not displayed from main
.extra-shadow {
position: fixed;
bottom: -100px;
z-index: 10;
width: 100vw;
height: 100px;
margin: 0 -20px;
//box-shadow: 0 -5px 100px $black-transparent;
}
diff --git a/app/components/AboutModal.js b/app/components/AboutModal.js
index 13c5d11..3a170ee 100644
--- a/app/components/AboutModal.js
+++ b/app/components/AboutModal.js
@@ -1,27 +1,27 @@
import React from 'react';
import { Text } from 'react-native';
import PropTypes from 'prop-types';
import { Modal, Portal, Surface, Title } from 'react-native-paper';
import styles from '../assets/styles/blink/_AboutModal.scss';
const AboutModal = (props) => {
return (
About Sylk
Sylk is the WebRTC client companion for SylkServer
- Copyright © AG Projects
+ © AG Projects
);
}
AboutModal.propTypes = {
show: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired
};
export default AboutModal;
diff --git a/app/components/AudioCallBox.js b/app/components/AudioCallBox.js
index 77b4cf5..8c63df1 100644
--- a/app/components/AudioCallBox.js
+++ b/app/components/AudioCallBox.js
@@ -1,205 +1,204 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import { IconButton } from 'react-native-paper';
import PropTypes from 'prop-types';
import debug from 'react-native-debug';
// const hark = require('hark');
import autoBind from 'auto-bind';
import CallOverlay from './CallOverlay';
import DTMFModal from './DTMFModal';
import EscalateConferenceModal from './EscalateConferenceModal';
import UserIcon from './UserIcon';
import styles from '../assets/styles/blink/_AudioCallBox.scss';
const DEBUG = debug('blinkrtc:AudioCallBox');
debug.enable('*');
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) {
DEBUG('Unmute microphone');
localStream.getAudioTracks()[0].enabled = true;
this.setState({audioMuted: false});
} else {
DEBUG('Mute microphone');
localStream.getAudioTracks()[0].enabled = false;
this.setState({audioMuted: true});
}
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
toggleEscalateConferenceModal() {
this.setState({
showEscalateConferenceModal: !this.state.showEscalateConferenceModal
});
}
render() {
let remoteIdentity;
if (this.props.call !== null) {
remoteIdentity = this.props.call.remoteIdentity;
} else {
remoteIdentity = {uri: this.props.remoteIdentity};
}
return (
-
-
+
+
- { this.props.call && this.props.call.state === 'established' ?
-
- : null }
+
);
}
}
AudioCallBox.propTypes = {
call : PropTypes.object,
escalateToConference : PropTypes.func,
hangupCall : PropTypes.func,
mediaPlaying : PropTypes.func,
remoteIdentity : PropTypes.string
};
-module.exports = AudioCallBox;
+export default AudioCallBox;
diff --git a/app/components/Call.js b/app/components/Call.js
index d9c91da..df58423 100644
--- a/app/components/Call.js
+++ b/app/components/Call.js
@@ -1,175 +1,171 @@
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 AudioCallBox from './AudioCallBox';
import LocalMedia from './LocalMedia';
import VideoBox from './VideoBox';
import config from '../config';
const DEBUG = debug('blinkrtc:Call');
debug.enable('*');
class Call extends Component {
constructor(props) {
super(props);
autoBind(this);
if (this.props.localMedia.getVideoTracks().length === 0) {
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) {
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) {
DEBUG('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});
} 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.getVideoTracks().length !== 0) {
DEBUG('Media type changed to video on accepted');
this.setState({audioOnly: false});
}
}
}
startCall() {
assert(this.props.currentCall === null, 'currentCall is not null');
let options = {pcConfig: {iceServers: config.iceServers}};
console.log('passing in a local Media stream', this.props.localMedia)
options.localStream = this.props.localMedia;
console.log('starting a call', this.props.targetUri);
let call = this.props.account.call(this.props.targetUri, options);
call.on('stateChanged', this.callStateChanged);
}
answerCall() {
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() {
this.props.hangupCall();
}
mediaPlaying() {
if (this.props.currentCall === null) {
this.startCall();
} else {
this.answerCall();
}
}
render() {
- let box;
+ let box = null;
let remoteIdentity;
if (this.props.currentCall !== null) {
remoteIdentity = this.props.currentCall.remoteIdentity.displayName || this.props.currentCall.remoteIdentity.uri;
} else {
remoteIdentity = 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 {
box = (
);
}
}
}
- return (
-
- {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
};
export default Call;
diff --git a/app/components/CallByUriBox.js b/app/components/CallByUriBox.js
index 0729724..aeb8e46 100644
--- a/app/components/CallByUriBox.js
+++ b/app/components/CallByUriBox.js
@@ -1,114 +1,103 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Title, Button, TextInput } from 'react-native-paper';
import autoBind from 'auto-bind';
import { View } from 'react-native';
import Call from './Call';
class CallByUriBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
displayName: ''
};
this._notificationCenter = null;
}
componentDidMount() {
this._notificationCenter = this.props.notificationCenter();
}
componentWillReceiveProps(nextProps) {
if (!this.props.currentCall && nextProps.currentCall) {
nextProps.currentCall.on('stateChanged', this.callStateChanged);
}
}
callStateChanged(oldState, newState, data) {
if (newState === 'terminated') {
this._notificationCenter.postSystemNotification('Thanks for calling with Sylk!', {timeout: 10});
}
}
handleDisplayNameChange(event) {
this.setState({displayName: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
this.props.handleCallByUri(this.state.displayName, this.props.targetUri);
}
render() {
const validInput = this.state.displayName !== '';
let content;
if (this.props.localMedia !== null) {
content = (
);
} else {
- const classes = classNames({
- 'capitalize' : true,
- 'btn' : true,
- 'btn-lg' : true,
- 'btn-block' : true,
- 'btn-default': !validInput,
- 'btn-primary': validInput
- });
-
content = (
-
+
You've been invited to call {this.props.targetUri}
-
+
-
-
+
+
);
}
return (
-
- {content}
-
+ {content}
);
}
}
CallByUriBox.propTypes = {
handleCallByUri : PropTypes.func.isRequired,
notificationCenter : PropTypes.func.isRequired,
hangupCall : PropTypes.func.isRequired,
shareScreen : PropTypes.func.isRequired,
targetUri : PropTypes.string,
localMedia : PropTypes.object,
account : PropTypes.object,
currentCall : PropTypes.object,
generatedVideoTrack : PropTypes.bool
};
-module.exports = CallByUriBox;
+export default CallByUriBox;
diff --git a/app/components/CallMeMaybeModal.js b/app/components/CallMeMaybeModal.js
index b58d150..b228ea2 100644
--- a/app/components/CallMeMaybeModal.js
+++ b/app/components/CallMeMaybeModal.js
@@ -1,74 +1,80 @@
import React, { Component } from 'react';
import { View, Linking } from 'react-native';
import PropTypes from 'prop-types';
import { Modal, Title, Surface, Portal, IconButton, Text } from 'react-native-paper';
import autoBind from 'auto-bind';
import utils from '../utils';
import styles from '../assets/styles/blink/_CallMeMaybeModal.scss';
class CallMeMaybeModal extends Component {
constructor(props) {
super(props);
autoBind(this);
const sipUri = this.props.callUrl.split('/').slice(-1)[0]; // hack!
const emailMessage = `You can call me using a Web browser at ${this.props.callUrl} or a SIP client at ${sipUri} ` +
'or by using the freely available Sylk WebRTC client app at http://sylkserver.com';
const subject = 'Call me, maybe?';
this.emailLink = `mailto:?subject=${encodeURI(subject)}&body=${encodeURI(emailMessage)}`;
}
handleClipboardButton(event) {
utils.copyToClipboard(this.props.callUrl);
this.props.notificationCenter().postSystemNotification('Call me, maybe?', {body: 'URL copied to the clipboard'});
this.props.close();
}
handleEmailButton(event) {
Linking.canOpenURL(this.emailLink)
.then((supported) => {
if (!supported) {
} else {
return Linking.openURL(url);
}
})
.catch((err) => {
this.props.notificationCenter().postSystemNotification('Call me, maybe?', {body: 'Unable to open email app'});
});
this.props.close();
}
render() {
return (
Call me, maybe?
Share {this.props.callUrl} with others so they can easily call you. You can copy it to the clipboard or send it via email.
-
-
-
-
-
+
+
+
);
}
}
CallMeMaybeModal.propTypes = {
show : PropTypes.bool.isRequired,
close : PropTypes.func.isRequired,
callUrl : PropTypes.string.isRequired,
notificationCenter : PropTypes.func.isRequired
};
export default CallMeMaybeModal;
diff --git a/app/components/CallOverlay.js b/app/components/CallOverlay.js
index cc3cb78..386306a 100644
--- a/app/components/CallOverlay.js
+++ b/app/components/CallOverlay.js
@@ -1,109 +1,105 @@
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';
class CallOverlay extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.duration = null;
this.timer = null;
this._isMounted = true;
}
componentDidMount() {
if (this.props.call) {
if (this.props.call.state === 'established') {
this.startTimer();
} else if (this.props.call.state !== 'terminated') {
this.props.call.on('stateChanged', this.callStateChanged);
}
}
}
componentWillReceiveProps(nextProps) {
if (this.props.call == null && nextProps.call) {
if (nextProps.call.state === 'established') {
this.startTimer();
} else if (nextProps.call.state !== 'terminated') {
nextProps.call.on('stateChanged', this.callStateChanged);
}
}
}
componentWillUnmount() {
this._isMounted = false;
clearTimeout(this.timer);
}
callStateChanged(oldState, newState, data) {
// Prevent starting timer when we are unmounted
if (newState === 'established' && this._isMounted) {
this.startTimer();
this.props.call.removeListener('stateChanged', this.callStateChanged);
}
}
startTimer() {
if (this.timer !== null) {
// already armed
return;
}
// TODO: consider using window.requestAnimationFrame
const startTime = new Date();
this.timer = setInterval(() => {
this.duration = moment.duration(new Date() - startTime).format('hh:mm:ss', {trim: false});
if (this.props.show) {
this.forceUpdate();
}
}, 300);
}
render() {
- let header;
+ let header = null;
if (this.props.show) {
let callDetail;
if (this.duration !== null) {
callDetail = {this.duration};
callDetail = this.duration;
} else {
callDetail = 'Connecting...'
}
header = (
);
}
- return (
-
- {header}
-
- );
+ return header
}
}
CallOverlay.propTypes = {
show: PropTypes.bool.isRequired,
remoteIdentity: PropTypes.string.isRequired,
call: PropTypes.object
};
-module.exports = CallOverlay;
+export default CallOverlay;
diff --git a/app/components/Conference.js b/app/components/Conference.js
index 4e2073e..cc12e99 100644
--- a/app/components/Conference.js
+++ b/app/components/Conference.js
@@ -1,109 +1,105 @@
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';
const DEBUG = debug('blinkrtc:Conference');
debug.enable('*');
class Conference extends React.Component {
constructor(props) {
super(props);
autoBind(this);
}
confStateChanged(oldState, newState, data) {
DEBUG(`Conference state changed ${oldState} -> ${newState}`);
if (newState === 'established') {
this.forceUpdate();
}
}
start() {
if (this.props.currentCall === null) {
const options = {
pcConfig: {iceServers: config.iceServers},
localStream: this.props.localMedia,
audio: true,
video: true,
offerOptions: {
offerToReceiveAudio: false,
offerToReceiveVideo: false
},
initialParticipants: this.props.participantsToInvite
};
DEBUG('Creating conference call', this.props.targetUri.toLowerCase(), options);
const confCall = this.props.account.joinConference(this.props.targetUri.toLowerCase(), options);
confCall.on('stateChanged', this.confStateChanged);
} else {
DEBUG('CALL ALREADY STARTED');
}
}
hangup() {
this.props.hangupCall();
}
mediaPlaying() {
if (this.props.currentCall === null) {
this.start();
} else {
DEBUG('CALL ALREADY STARTED');
}
}
render() {
- let box;
+ let box = null;
if (this.props.localMedia !== null) {
if (this.props.currentCall != null && this.props.currentCall.state === 'established') {
box = (
);
} else {
box = (
);
}
}
- return (
-
- {box}
-
- );
+ return box;
}
}
Conference.propTypes = {
notificationCenter : PropTypes.func.isRequired,
account : PropTypes.object.isRequired,
hangupCall : PropTypes.func.isRequired,
currentCall : PropTypes.object,
localMedia : PropTypes.object,
targetUri : PropTypes.string,
participantsToInvite : PropTypes.array,
generatedVideoTrack : PropTypes.bool
};
export default Conference;
diff --git a/app/components/ConferenceBox.js b/app/components/ConferenceBox.js
index d3ba58c..9068248 100644
--- a/app/components/ConferenceBox.js
+++ b/app/components/ConferenceBox.js
@@ -1,682 +1,687 @@
'use strict';
import React, {Component, Fragment} from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import * as sylkrtc from 'sylkrtc';
import classNames from 'classnames';
import debug from 'react-native-debug';
import superagent from 'superagent';
import autoBind from 'auto-bind';
import { RTCView } from 'react-native-webrtc';
import { IconButton, Appbar, Portal, Modal, Surface, Paragraph } from 'react-native-paper';
import config from '../config';
import utils from '../utils';
//import AudioPlayer from './AudioPlayer';
import ConferenceDrawer from './ConferenceDrawer';
import ConferenceDrawerLog from './ConferenceDrawerLog';
import ConferenceDrawerFiles from './ConferenceDrawerFiles';
import ConferenceDrawerParticipant from './ConferenceDrawerParticipant';
import ConferenceDrawerParticipantList from './ConferenceDrawerParticipantList';
import ConferenceDrawerSpeakerSelection from './ConferenceDrawerSpeakerSelection';
import ConferenceHeader from './ConferenceHeader';
import ConferenceCarousel from './ConferenceCarousel';
import ConferenceParticipant from './ConferenceParticipant';
import ConferenceMatrixParticipant from './ConferenceMatrixParticipant';
import ConferenceParticipantSelf from './ConferenceParticipantSelf';
import InviteParticipantsModal from './InviteParticipantsModal';
import styles from '../assets/styles/blink/_ConferenceBox.scss';
const DEBUG = debug('blinkrtc:ConferenceBox');
debug.enable('*');
class ConferenceBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
callOverlayVisible: true,
audioMuted: false,
videoMuted: false,
participants: props.call.participants.slice(),
showInviteModal: false,
showDrawer: false,
showFiles: false,
shareOverlayVisible: false,
activeSpeakers: props.call.activeParticipants.slice(),
selfDisplayedLarge: false,
eventLog: [],
sharedFiles: props.call.sharedFiles.slice(),
largeVideoStream: null
};
const friendlyName = this.props.remoteIdentity.split('@')[0];
if (window.location.origin.startsWith('file://')) {
this.callUrl = `${config.publicUrl}/conference/${friendlyName}`;
} else {
this.callUrl = `${window.location.origin}/conference/${friendlyName}`;
}
const emailMessage = `You can join me in the conference using a Web browser at ${this.callUrl} ` +
'or by using the freely available Sylk WebRTC client app at http://sylkserver.com';
const subject = 'Join me, maybe?';
this.emailLink = `mailto:?subject=${encodeURI(subject)}&body=${encodeURI(emailMessage)}`;
this.overlayTimer = null;
this.logEvent = {};
this.haveVideo = false;
this.uploads = [];
[
'error',
'warning',
'info',
'debug'
].forEach((level) => {
this.logEvent[level] = (
(action, messages, originator) => {
const log = this.state.eventLog.slice();
log.unshift({originator, originator, level: level, action: action, messages: messages});
this.setState({eventLog: log});
}
);
});
}
componentDidMount() {
for (let p of this.state.participants) {
p.on('stateChanged', this.onParticipantStateChanged);
p.attach();
}
this.props.call.on('participantJoined', this.onParticipantJoined);
this.props.call.on('participantLeft', this.onParticipantLeft);
this.props.call.on('roomConfigured', this.onConfigureRoom);
this.props.call.on('fileSharing', this.onFileSharing);
this.armOverlayTimer();
// attach to ourselves first if there are no other participants
if (this.state.participants.length === 0) {
setTimeout(() => {
const item = {
stream: this.props.call.getLocalStreams()[0],
identity: this.props.call.localIdentity
};
this.selectVideo(item);
});
} else {
// this.changeResolution();
}
if (this.props.call.getLocalStreams()[0].getVideoTracks().length !== 0) {
this.haveVideo = true;
}
}
componentWillUnmount() {
clearTimeout(this.overlayTimer);
this.uploads.forEach((upload) => {
this.props.notificationCenter().removeNotification(upload[1]);
upload[0].abort();
})
}
onParticipantJoined(p) {
DEBUG(`Participant joined: ${p.identity}`);
// this.refs.audioPlayerParticipantJoined.play();
p.on('stateChanged', this.onParticipantStateChanged);
p.attach();
this.setState({
participants: this.state.participants.concat([p])
});
// this.changeResolution();
}
onParticipantLeft(p) {
DEBUG(`Participant left: ${p.identity}`);
// this.refs.audioPlayerParticipantLeft.play();
const participants = this.state.participants.slice();
const idx = participants.indexOf(p);
if (idx !== -1) {
participants.splice(idx, 1);
this.setState({
participants: participants
});
}
p.detach(true);
// this.changeResolution();
}
onParticipantStateChanged(oldState, newState) {
if (newState === 'established' || newState === null) {
this.maybeSwitchLargeVideo();
}
}
onConfigureRoom(config) {
const newState = {};
newState.activeSpeakers = config.activeParticipants;
this.setState(newState);
if (config.activeParticipants.length === 0) {
this.logEvent.info('set speakers to', ['Nobody'], config.originator);
} else {
const speakers = config.activeParticipants.map((p) => {return p.identity.displayName || p.identity.uri});
this.logEvent.info('set speakers to', speakers, config.originator);
}
this.maybeSwitchLargeVideo();
}
onFileSharing(files) {
let stateFiles = this.state.sharedFiles.slice();
stateFiles = stateFiles.concat(files);
this.setState({sharedFiles: stateFiles});
files.forEach((file)=>{
if (file.session !== this.props.call.id) {
this.props.notificationCenter().postFileShared(file, this.showFiles);
}
})
}
changeResolution() {
let stream = this.props.call.getLocalStreams()[0];
if (this.state.participants.length < 2) {
this.props.call.scaleLocalTrack(stream, 1.5);
} else if (this.state.participants.length < 5) {
this.props.call.scaleLocalTrack(stream, 2);
} else {
this.props.call.scaleLocalTrack(stream, 1);
}
}
selectVideo(item) {
DEBUG('Switching video to: %o', item);
if (item.stream) {
this.setState({selfDisplayedLarge: true, largeVideoStream: item.stream});
}
}
maybeSwitchLargeVideo() {
// Switch the large video to another source, maybe.
if (this.state.participants.length === 0 && !this.state.selfDisplayedLarge) {
// none of the participants are eligible, show ourselves
const item = {
stream: this.props.call.getLocalStreams()[0],
identity: this.props.call.localIdentity
};
this.selectVideo(item);
} else if (this.state.selfDisplayedLarge) {
this.setState({selfDisplayedLarge: false});
}
}
handleClipboardButton() {
utils.copyToClipboard(this.callUrl);
this.props.notificationCenter().postSystemNotification('Join me, maybe?', {body: 'Link copied to the clipboard'});
this.setState({shareOverlayVisible: false});
}
handleEmailButton(event) {
// if (navigator.userAgent.indexOf('Chrome') > 0) {
// let emailWindow = window.open(this.emailLink, '_blank');
// setTimeout(() => {
// emailWindow.close();
// }, 500);
// } else {
// window.open(this.emailLink, '_self');
// }
this.setState({shareOverlayVisible: false});
}
handleShareOverlayEntered() {
this.setState({shareOverlayVisible: true});
}
handleShareOverlayExited() {
this.setState({shareOverlayVisible: false});
}
handleActiveSpeakerSelected(participant, secondVideo=false) { // eslint-disable-line space-infix-ops
let newActiveSpeakers = this.state.activeSpeakers.slice();
if (secondVideo) {
if (participant.id !== 'none') {
if (newActiveSpeakers.length >= 1) {
newActiveSpeakers[1] = participant;
} else {
newActiveSpeakers[0] = participant;
}
} else {
newActiveSpeakers.splice(1,1);
}
} else {
if (participant.id !== 'none') {
newActiveSpeakers[0] = participant;
} else {
newActiveSpeakers.shift();
}
}
this.props.call.configureRoom(newActiveSpeakers.map((element) => element.publisherId), (error) => {
if (error) {
// This causes a state update, hence the drawer lists update
this.logEvent.error('set speakers failed', [], this.localIdentity);
}
});
}
handleDrop(files) {
DEBUG('Dropped file %o', files);
this.uploadFiles(files);
};
handleFiles(e) {
DEBUG('Selected files %o', e.target.files);
this.uploadFiles(e.target.files);
event.target.value = '';
}
uploadFiles(files) {
for (var key in files) {
// is the item a File?
if (files.hasOwnProperty(key) && files[key] instanceof File) {
let uploadRequest;
let complete = false;
const filename = files[key].name
let progressNotification = this.props.notificationCenter().postFileUploadProgress(
filename,
(notification) => {
if (!complete) {
uploadRequest.abort();
this.uploads.splice(this.uploads.indexOf(uploadRequest), 1);
}
}
);
uploadRequest = superagent
.post(`${config.fileSharingUrl}/${this.props.remoteIdentity}/${this.props.call.id}/${filename}`)
.send(files[key])
.on('progress', (e) => {
this.props.notificationCenter().editFileUploadNotification(e.percent, progressNotification);
})
.end((err, response) => {
complete = true;
this.props.notificationCenter().removeFileUploadNotification(progressNotification);
if (err) {
this.props.notificationCenter().postFileUploadFailed(filename);
}
this.uploads.splice(this.uploads.indexOf(uploadRequest), 1);
});
this.uploads.push([uploadRequest, progressNotification]);
}
}
}
downloadFile(filename) {
// const a = document.createElement('a');
// a.href = `${config.fileSharingUrl}/${this.props.remoteIdentity}/${this.props.call.id}/${filename}`;
// a.target = '_blank';
// a.download = filename;
// const clickEvent = document.createEvent('MouseEvent');
// clickEvent.initMouseEvent('click', true, true, window, 0,
// clickEvent.screenX, clickEvent.screenY, clickEvent.clientX, clickEvent.clientY,
// clickEvent.ctrlKey, clickEvent.altKey, clickEvent.shiftKey, clickEvent.metaKey,
// 0, null);
// a.dispatchEvent(clickEvent);
}
preventOverlay(event) {
// Stop the overlay when we are the thumbnail bar
event.stopPropagation();
}
muteAudio(event) {
event.preventDefault();
const localStream = this.props.call.getLocalStreams()[0];
if (localStream.getAudioTracks().length > 0) {
const track = localStream.getAudioTracks()[0];
if(this.state.audioMuted) {
DEBUG('Unmute microphone');
track.enabled = true;
this.setState({audioMuted: false});
} else {
DEBUG('Mute microphone');
track.enabled = false;
this.setState({audioMuted: true});
}
}
}
muteVideo(event) {
event.preventDefault();
const localStream = this.props.call.getLocalStreams()[0];
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});
}
}
}
hangup(event) {
event.preventDefault();
for (let participant of this.state.participants) {
participant.detach();
}
this.props.hangup();
}
armOverlayTimer() {
clearTimeout(this.overlayTimer);
this.overlayTimer = setTimeout(() => {
this.setState({callOverlayVisible: false});
}, 4000);
}
showOverlay() {
// if (!this.state.shareOverlayVisible && !this.state.showDrawer && !this.state.showFiles) {
// if (!this.state.callOverlayVisible) {
// this.setState({callOverlayVisible: true});
// }
// this.armOverlayTimer();
// }
}
toggleInviteModal() {
// this.setState({showInviteModal: !this.state.showInviteModal});
// if (this.refs.showOverlay) {
// this.refs.shareOverlay.hide();
// }
}
toggleDrawer() {
this.setState({callOverlayVisible: true, showDrawer: !this.state.showDrawer, showFiles: false});
clearTimeout(this.overlayTimer);
}
toggleFiles() {
this.setState({callOverlayVisible: true, showFiles: !this.state.showFiles, showDrawer: false});
clearTimeout(this.overlayTimer);
}
showFiles() {
this.setState({callOverlayVisible: true, showFiles: true, showDrawer: false});
clearTimeout(this.overlayTimer);
}
render() {
if (this.props.call === null) {
return ();
}
let watermark;
const largeVideoClasses = classNames({
'animated' : true,
'fadeIn' : true,
'large' : true,
'mirror' : !this.props.call.sharingScreen && !this.props.generatedVideoTrack,
'fit' : this.props.call.sharingScreen
});
let matrixClasses = classNames({
'matrix' : true
});
const containerClasses = classNames({
'video-container': true,
'conference': true,
'drawer-visible': this.state.showDrawer || this.state.showFiles
});
const remoteIdentity = this.props.remoteIdentity.split('@')[0];
const shareOverlay = (
Invite other online users of this service, share this link with others or email, so they can easily join this conference.
-
+
);
const buttons = {};
// const commonButtonTopClasses = classNames({
// 'btn' : true,
// 'btn-link' : true
// });
// const fullScreenButtonIcons = classNames({
// 'fa' : true,
// 'fa-2x' : true,
// 'fa-expand' : !this.isFullScreen(),
// 'fa-compress' : this.isFullScreen()
// });
const topButtons = [];
// if (!this.state.showFiles) {
// if (this.state.sharedFiles.length !== 0) {
// topButtons.push(
//
//
//
// );
// }
// }
if (!this.state.showDrawer) {
topButtons.push();
}
buttons.top = {right: topButtons};
const muteButtonIcons = this.state.audioMuted ? 'microphone-off' : 'microphone';
const muteVideoButtonIcons = this.state.videoMuted ? 'video-off' : 'video';
const bottomButtons = [];
bottomButtons.push(
-
+
);
- bottomButtons.push();
- bottomButtons.push();
bottomButtons.push(
-
-
-
+
+ );
+ bottomButtons.push(
+
+ );
+ // bottomButtons.push(
+ //
+ //
+ //
+ // );
+ bottomButtons.push(
+
);
- bottomButtons.push();
buttons.bottom = bottomButtons;
const participants = [];
if (this.state.participants.length > 0) {
if (this.state.activeSpeakers.findIndex((element) => {return element.id === this.props.call.id}) === -1) {
participants.push(
);
}
}
const drawerParticipants = [];
drawerParticipants.push(
);
let videos = [];
if (this.state.participants.length === 0) {
videos.push(
-
+
);
} else {
const activeSpeakers = this.state.activeSpeakers;
const activeSpeakersCount = activeSpeakers.length;
- matrixClasses = classNames({
- 'matrix' : true,
- 'one-row' : activeSpeakersCount === 2,
- 'two-columns' : activeSpeakersCount === 2
- });
if (activeSpeakersCount > 0) {
activeSpeakers.forEach((p) => {
videos.push(
);
});
this.state.participants.forEach((p) => {
if (this.state.activeSpeakers.indexOf(p) === -1) {
participants.push(
);
}
drawerParticipants.push(
);
});
} else {
- matrixClasses = classNames({
- 'matrix' : true,
- 'one-row' : this.state.participants.length === 2 ,
- 'two-row' : this.state.participants.length >= 3 && this.state.participants.length < 7,
- 'three-row' : this.state.participants.length >= 7,
- 'two-columns' : this.state.participants.length >= 2 || this.state.participants.length <= 4,
- 'three-columns' : this.state.participants.length > 4
- });
+
this.state.participants.forEach((p) => {
videos.push(
);
drawerParticipants.push(
);
});
}
}
- let drawerContent = (
-
-
-
- {drawerParticipants}
-
-
-
- );
-
let filesDrawerContent = (
);
- let appbar = (
-
- );
-
return (
-
- {appbar}
- {/* */}
-
-
+
+
+
+
{videos}
{participants}
- {drawerContent}
+
+
+ {drawerParticipants}
+
+
);
}
}
ConferenceBox.propTypes = {
notificationCenter : PropTypes.func.isRequired,
call : PropTypes.object,
hangup : PropTypes.func,
remoteIdentity : PropTypes.string,
generatedVideoTrack : PropTypes.bool
};
export default ConferenceBox;
diff --git a/app/components/ConferenceByUriBox.js b/app/components/ConferenceByUriBox.js
index e5174d3..a8e375b 100644
--- a/app/components/ConferenceByUriBox.js
+++ b/app/components/ConferenceByUriBox.js
@@ -1,124 +1,122 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import autoBind from 'auto-bind';
import { View } from 'react-native';
import { Title, Button, TextInput } from 'react-native-paper';
import Conference from './Conference';
class ConferenceByUriBox extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
displayName: ''
};
this._notificationCenter = null;
}
componentDidMount() {
this._notificationCenter = this.props.notificationCenter();
}
componentWillReceiveProps(nextProps) {
if (!this.props.currentCall && nextProps.currentCall) {
nextProps.currentCall.on('stateChanged', this.callStateChanged);
}
}
callStateChanged(oldState, newState, data) {
if (newState === 'terminated') {
this._notificationCenter.postSystemNotification('Thanks for calling with Sylk!', {timeout: 10});
}
}
handleDisplayNameChange(event) {
this.setState({displayName: event.target.value});
}
handleSubmit(event) {
event.preventDefault();
let displayName;
if (this.state.displayName === '') {
this.setState({displayName: 'Guest'});
displayName = 'Guest';
} else {
displayName = this.state.displayName;
// Bug in SIPSIMPLE, display name can't end with \ else we don't join chat
if (displayName.endsWith('\\')) {
displayName = displayName.slice(0, -1);
}
}
this.props.handler(displayName, this.props.targetUri);
}
render() {
let content;
if (this.props.localMedia !== null) {
content = (
);
} else {
const classes = classNames({
'capitalize' : true,
'btn' : true,
'btn-lg' : true,
'btn-block' : true,
'btn-primary': true
});
const friendlyName = this.props.targetUri.split('@')[0];
content = (
-
+
You're about to join a conference! {friendlyName}
-
-
-
+
+
);
}
return (
-
-
- {content}
-
+
+ {content}
);
}
}
ConferenceByUriBox.propTypes = {
notificationCenter : PropTypes.func.isRequired,
handler : PropTypes.func.isRequired,
hangupCall : PropTypes.func.isRequired,
shareScreen : PropTypes.func.isRequired,
targetUri : PropTypes.string,
localMedia : PropTypes.object,
account : PropTypes.object,
currentCall : PropTypes.object,
generatedVideoTrack : PropTypes.bool
};
-module.exports = ConferenceByUriBox;
+export default ConferenceByUriBox;
diff --git a/app/components/ConferenceCarousel.js b/app/components/ConferenceCarousel.js
index d4287cf..1da1b6e 100644
--- a/app/components/ConferenceCarousel.js
+++ b/app/components/ConferenceCarousel.js
@@ -1,42 +1,38 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Dimensions } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import styles from '../assets/styles/blink/_ConferenceCarousel.scss';
let {height, width} = Dimensions.get('window');
class ConferenceCarousel extends Component {
constructor(props) {
super(props);
}
render() {
return (
{ this._carousel = c; }}
data={this.props.children}
renderItem={({item}) => {
//add in some styles on the View
- return (
-
- { item }
-
- );
+ return item;
}}
sliderWidth={width}
- itemWidth={100}
+ itemWidth={130}
itemHeight={100}
/>
);
}
}
ConferenceCarousel.propTypes = {
children: PropTypes.node,
align: PropTypes.string
};
export default ConferenceCarousel;
diff --git a/app/components/ConferenceDrawerFiles.js b/app/components/ConferenceDrawerFiles.js
index 0901f9b..b01184a 100644
--- a/app/components/ConferenceDrawerFiles.js
+++ b/app/components/ConferenceDrawerFiles.js
@@ -1,46 +1,45 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
-import { View } from 'react-native';
//import utils from '../utils';
import { List, IconButton } from 'react-native-paper';
const ConferenceDrawerFiles = (props) => {
const entries = props.sharedFiles.slice(0).reverse().map((elem, idx) => {
const uploader = elem.uploader.displayName || elem.uploader.uri || elem.uploader;
//const color = utils.generateMaterialColor(elem.uploader.uri || elem.uploader)['300'];
let size = (elem.filesize / 1048576).toFixed(2);
return (
}
right={props => {props.downloadFile(elem.filename)}}
/>}
/>
);
});
return (
-
+
Shared Files
{entries}
-
+
);
};
ConferenceDrawerFiles.propTypes = {
sharedFiles: PropTypes.array.isRequired,
downloadFile: PropTypes.func.isRequired
};
export default ConferenceDrawerFiles;
diff --git a/app/components/ConferenceDrawerLog.js b/app/components/ConferenceDrawerLog.js
index c48704f..e1306f8 100644
--- a/app/components/ConferenceDrawerLog.js
+++ b/app/components/ConferenceDrawerLog.js
@@ -1,46 +1,45 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import utils from '../utils';
import { Title } from 'react-native-paper';
-import { View } from 'react-native';
const ConferenceDrawerLog = (props) => {
const entries = props.log.map((elem, idx) => {
// const classes = classNames({
// 'text-danger' : elem.level === 'error',
// 'text-warning' : elem.level === 'warning',
// 'log-entry' : true
// });
const originator = elem.originator.displayName || elem.originator.uri || elem.originator;
const messages = elem.messages.map((message, index) => {
return {message}
;
});
const color = utils.generateMaterialColor(elem.originator.uri || elem.originator)['300'];
return null;
// return (
//
// <{props.log.length - idx}
//
// {originator} {elem.action}
{messages}
//
// )
});
return (
-
+
Configuration Events
{entries}
-
+
);
};
ConferenceDrawerLog.propTypes = {
log: PropTypes.array.isRequired
};
export default ConferenceDrawerLog;
diff --git a/app/components/ConferenceDrawerParticipantList.js b/app/components/ConferenceDrawerParticipantList.js
index f220ece..69a3c16 100644
--- a/app/components/ConferenceDrawerParticipantList.js
+++ b/app/components/ConferenceDrawerParticipantList.js
@@ -1,29 +1,27 @@
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { List } from 'react-native-paper';
const ConferenceDrawerParticipantList = (props) => {
const items = [];
let idx = 0;
React.Children.forEach(props.children, (child) => {
items.push();
idx++;
});
return (
-
-
- Participants
- {items}
-
-
+
+ Participants
+ {items}
+
);
};
ConferenceDrawerParticipantList.propTypes = {
children: PropTypes.node
};
export default ConferenceDrawerParticipantList;
diff --git a/app/components/ConferenceDrawerSpeakerSelection.js b/app/components/ConferenceDrawerSpeakerSelection.js
index 7b9e2a0..e1be911 100644
--- a/app/components/ConferenceDrawerSpeakerSelection.js
+++ b/app/components/ConferenceDrawerSpeakerSelection.js
@@ -1,123 +1,123 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
import { Divider, Menu, Title } from 'react-native-paper';
class ConferenceDrawerSpeakerSelection extends Component {
constructor(props) {
super(props);
autoBind(this)
this.state = {
speakers: props.activeSpeakers.map((participant) => {return participant.id})
};
}
componentWillReceiveProps(nextProps) {
let speakers = [];
if (nextProps.activeSpeakers.length !== 0) {
speakers = nextProps.activeSpeakers.map((participant) => {
return participant.id
});
}
this.setState({speakers: speakers});
}
handleFirstSpeakerSelected(event) {
if (event === 'none') {
if (this.state.speakers.length > 0) {
this.props.selected({ id: event});
const newSpeakers = this.state.speakers.slice(1);
this.setState({speakers: newSpeakers});
}
} else {
if (this.state.speakers[0] !== this.props.participants[event].id) {
this.props.selected(this.props.participants[event]);
const newSpeakers = this.state.speakers.slice();
newSpeakers[0] = this.props.participants[event].id;
this.setState({speakers: newSpeakers});
}
}
}
handleSecondSpeakerSelected(event) {
if (event === 'none') {
if (this.state.speakers.length > 1) {
this.props.selected({ id: event}, true);
const newSpeakers = this.state.speakers.slice();
newSpeakers.pop();
this.setState({speakers: newSpeakers});
}
} else {
const newSpeakers = this.state.speakers.slice();
newSpeakers[1] = this.props.participants[event].id;
this.setState({speakers: newSpeakers});
this.props.selected(this.props.participants[event], true);
}
}
render() {
const participantsLeft = [];
const participantsRight = [];
let title1 = 'None';
let title2 = 'None';
participantsLeft.push();
this.props.participants.forEach((p, index) => {
let title = p.identity.displayName || p.identity.uri;
if (this.state.speakers[0] === p.id) {
participantsLeft.push(
);
title1 = title;
} else if (this.state.speakers[1] === p.id) {
participantsRight.push(
);
title2 = title;
} else {
participantsRight.push(
);
participantsLeft.push(
);
}
});
if (participantsRight.length !== 0) {
participantsRight.unshift();
}
return (
-
+
Active Speakers
{/* */}
{/* */}
-
+
);
}
}
ConferenceDrawerSpeakerSelection.propTypes = {
participants: PropTypes.array.isRequired,
selected: PropTypes.func,
activeSpeakers: PropTypes.array
};
export default ConferenceDrawerSpeakerSelection;
diff --git a/app/components/ConferenceHeader.js b/app/components/ConferenceHeader.js
index a4f12eb..a44b8db 100644
--- a/app/components/ConferenceHeader.js
+++ b/app/components/ConferenceHeader.js
@@ -1,86 +1,84 @@
import React, { useState, useEffect, useRef, Fragment } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment';
import momentFormat from 'moment-duration-format';
import { Text, Appbar } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import styles from '../assets/styles/blink/_ConferenceHeader.scss';
const useInterval = (callback, delay) => {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const ConferenceHeader = (props) => {
let [seconds, setSeconds] = useState(0);
useInterval(() => {
setSeconds(seconds + 1);
}, 1000);
const duration = moment.duration(seconds, 'seconds').format('hh:mm:ss', {trim: false});
let videoHeader;
let callButtons;
if (props.show) {
const participantCount = props.participants.length + 1;
const callDetail = (
{duration} - {participantCount} participant{participantCount > 1 ? 's' : ''}
);
videoHeader = (
-
-
-
- {props.buttons.top.right}
-
-
+
+
+ {props.buttons.top.right}
+
);
callButtons = (
-
+
{props.buttons.bottom}
);
}
return (
-
+
{videoHeader}
{callButtons}
);
}
ConferenceHeader.propTypes = {
show: PropTypes.bool.isRequired,
remoteIdentity: PropTypes.string.isRequired,
participants: PropTypes.array.isRequired,
buttons: PropTypes.object.isRequired
};
export default ConferenceHeader;
diff --git a/app/components/ConferenceMatrixParticipant.js b/app/components/ConferenceMatrixParticipant.js
index 2002f6a..41c8b63 100644
--- a/app/components/ConferenceMatrixParticipant.js
+++ b/app/components/ConferenceMatrixParticipant.js
@@ -1,140 +1,138 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
// const hark = require('hark');
import classNames from 'classnames';
import autoBind from 'auto-bind';
import { Paragraph } from 'react-native-paper';
import { RTCView } from 'react-native-webrtc';
import { View } from 'react-native';
import styles from '../assets/styles/blink/_ConferenceMatrixParticipant.scss';
class ConferenceMatrixParticipant extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
active: false,
hasVideo: false,
sharesScreen: false,
audioMuted: false,
stream: null
}
this.speechEvents = null;
this.videoElement = React.createRef();
if (!props.isLocal) {
props.participant.on('stateChanged', this.onParticipantStateChanged);
}
}
componentDidMount() {
this.maybeAttachStream();
// this.videoElement.current.oncontextmenu = (e) => {
// // disable right click for video elements
// e.preventDefault();
// };
// this.videoElement.current.onresize = (event) => {
// this.handleResize(event);
// };
}
componentWillUnmount() {
if (!this.props.isLocal) {
this.props.participant.removeListener('stateChanged', this.onParticipantStateChanged);
}
// if (this.speechEvents !== null) {
// this.speechEvents.stop();
// this.speechEvents = null;
// }
}
onParticipantStateChanged(oldState, newState) {
if (newState === 'established') {
this.maybeAttachStream();
}
}
handleResize(event) {
// console.log(event.srcElement.videoWidth);
const resolutions = ['1280x720', '960x540', '640x480', '640x360', '480x270', '320x180'];
if (this.state.hasVideo) {
const videoResolution = event.target.videoWidth + 'x' + event.target.videoHeight;
if (resolutions.indexOf(videoResolution) === -1) {
this.setState({sharesScreen: true});
} else {
this.setState({sharesScreen: false});
}
}
}
maybeAttachStream() {
const streams = this.props.participant.streams;
console.log(this.props.participant);
if (streams.length > 0) {
this.setState({stream: streams[0], hasVideo: streams[0].getVideoTracks().length > 0});
// const options = {
// interval: 150,
// play: false
// };
// this.speechEvents = hark(streams[0], options);
// this.speechEvents.on('speaking', () => {
// this.setState({active: true});
// });
// this.speechEvents.on('stopped_speaking', () => {
// this.setState({active: false});
// });
}
}
render() {
// const classes = classNames({
// 'poster' : !this.state.hasVideo,
// 'fit' : this.state.sharesScreen
// });
// const remoteVideoClasses = classNames({
// 'remote-video' : true,
// 'large' : this.props.large,
// 'conference-active' : this.state.active
// });
const participantInfo = (
{this.props.participant.identity.displayName || this.props.participant.identity.uri}
);
let activeIcon;
if (this.props.isLocal) {
activeIcon = (
Speaker
);
}
- console.log(this.state.stream);
-
return (
-
+
{activeIcon}
{/* {participantInfo} */}
-
-
+
+
);
}
}
ConferenceMatrixParticipant.propTypes = {
participant: PropTypes.object.isRequired,
large: PropTypes.bool,
isLocal: PropTypes.bool
};
export default ConferenceMatrixParticipant;
diff --git a/app/components/ConferenceModal.js b/app/components/ConferenceModal.js
index 29f15d7..e16f7d5 100644
--- a/app/components/ConferenceModal.js
+++ b/app/components/ConferenceModal.js
@@ -1,76 +1,77 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Portal, Modal, Title, Button, Text, TextInput, Surface } from 'react-native-paper';
import config from '../config';
import styles from '../assets/styles/blink/_ConferenceModal.scss';
class ConferenceModal extends Component {
constructor(props) {
super(props);
this.state = {
conferenceTargetUri: props.targetUri.split('@')[0],
managed: false
};
this.handleConferenceTargetChange = this.handleConferenceTargetChange.bind(this);
this.onHide = this.onHide.bind(this);
this.join = this.join.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({conferenceTargetUri: nextProps.targetUri.split('@')[0]});
}
handleConferenceTargetChange(value) {
this.setState({conferenceTargetUri: value});
}
join(event) {
event.preventDefault();
const uri = `${this.state.conferenceTargetUri.replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`;
this.props.handleConferenceCall(uri.toLowerCase(), this.state.managed);
}
onHide() {
this.props.handleConferenceCall(null);
}
render() {
const validUri = this.state.conferenceTargetUri.length > 0 && this.state.conferenceTargetUri.indexOf('@') === -1;
return (
Join Video Conference
Enter the conference room you wish to join
);
}
}
ConferenceModal.propTypes = {
show: PropTypes.bool.isRequired,
handleConferenceCall: PropTypes.func.isRequired,
targetUri: PropTypes.string.isRequired
};
export default ConferenceModal;
diff --git a/app/components/ConferenceParticipant.js b/app/components/ConferenceParticipant.js
index e540708..659e147 100644
--- a/app/components/ConferenceParticipant.js
+++ b/app/components/ConferenceParticipant.js
@@ -1,133 +1,133 @@
import React from 'react';
import PropTypes from 'prop-types';
// const hark = require('hark');
import classNames from 'classnames';
import autoBind from 'auto-bind';
import { IconButton } from 'react-native-paper';
import { RTCView } from 'react-native-webrtc';
-
+import styles from '../assets/styles/blink/_ConferenceParticipant.scss';
class ConferenceParticipant extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
active: false,
hasVideo: false,
overlayVisible: false,
audioMuted: false,
stream: null
}
this.speechEvents = null;
this.videoElement = React.createRef();
props.participant.on('stateChanged', this.onParticipantStateChanged);
}
componentDidMount() {
this.maybeAttachStream();
// this.videoElement.current.oncontextmenu = (e) => {
// // disable right click for video elements
// e.preventDefault();
// };
}
componentWillUnmount() {
this.videoElement.current.pause();
this.props.participant.removeListener('stateChanged', this.onParticipantStateChanged);
if (this.speechEvents !== null) {
this.speechEvents.stop();
this.speechEvents = null;
}
}
onParticipantStateChanged(oldState, newState) {
if (newState === 'established') {
this.maybeAttachStream();
}
}
onMuteAudioClicked(event) {
event.preventDefault();
const streams = this.props.participant.streams;
if (streams[0].getAudioTracks().length > 0) {
const track = streams[0].getAudioTracks()[0];
if(this.state.audioMuted) {
track.enabled = true;
this.setState({audioMuted: false});
} else {
track.enabled = false;
this.setState({audioMuted: true});
}
}
}
maybeAttachStream() {
const streams = this.props.participant.streams;
if (streams.length > 0) {
this.setState({stream: streams[0], hasVideo: streams[0].getVideoTracks().length > 0});
// const options = {
// interval: 150,
// play: false
// };
// this.speechEvents = hark(streams[0], options);
// this.speechEvents.on('speaking', () => {
// this.setState({active: true});
// });
// this.speechEvents.on('stopped_speaking', () => {
// this.setState({active: false});
// });
}
}
showOverlay() {
this.setState({overlayVisible: true});
}
hideOverlay() {
if (!this.state.audioMuted) {
this.setState({overlayVisible: false});
}
}
render() {
// const tooltip = (
// {this.props.participant.identity.displayName || this.props.participant.identity.uri}
// );
const classes = classNames({
'poster' : !this.state.hasVideo,
'conference-active' : this.state.active
});
let muteButton;
if (this.state.overlayVisible) {
const muteButtonIcons = this.state.audioMuted ? 'microphone-off' : 'microphone';
muteButton = (
);
}
return (
-
+
{muteButton}
{/* */}
-
-
+
+
{/* */}
);
}
}
ConferenceParticipant.propTypes = {
participant: PropTypes.object.isRequired
};
export default ConferenceParticipant;
diff --git a/app/components/ConferenceParticipantSelf.js b/app/components/ConferenceParticipantSelf.js
index 4e20d1a..4dd8d43 100644
--- a/app/components/ConferenceParticipantSelf.js
+++ b/app/components/ConferenceParticipantSelf.js
@@ -1,98 +1,98 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
//const hark = require('hark');
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import { RTCView } from 'react-native-webrtc';
import { Card } from 'react-native-paper';
import styles from '../assets/styles/blink/_ConferenceParticipantSelf.scss';
class ConferenceParticipantSelf extends Component {
constructor(props) {
super(props);
this.state = {
active: false,
hasVideo: false,
sharesScreen: false,
}
// this.speechEvents = null;
}
componentDidMount() {
// factor it out to a function to avoid lint warning about calling setState here
this.attachSpeechEvents();
// this.refs.videoElement.onresize = (event) => {
// this.handleResize(event)
// };
}
handleResize(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({sharesScreen: true});
} else {
this.setState({sharesScreen: false});
}
}
componentWillUnmount() {
// if (this.speechEvents !== null) {
// this.speechEvents.stop();
// this.speechEvents = null;
// }
}
attachSpeechEvents() {
this.setState({hasVideo: this.props.stream.getVideoTracks().length > 0});
// const options = {
// interval: 150,
// play: false
// };
// this.speechEvents = hark(this.props.stream, options);
// this.speechEvents.on('speaking', () => {
// this.setState({active: true});
// });
// this.speechEvents.on('stopped_speaking', () => {
// this.setState({active: false});
// });
}
render() {
if (this.props.stream == null) {
return false;
}
// const tooltip = (
// {this.props.identity.displayName || this.props.identity.uri}
// );
let muteIcon
if (this.props.audioMuted) {
muteIcon = (
);
}
return (
{muteIcon}
-
+
);
}
}
ConferenceParticipantSelf.propTypes = {
stream: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
audioMuted: PropTypes.bool.isRequired,
generatedVideoTrack: PropTypes.bool
};
export default ConferenceParticipantSelf;
diff --git a/app/components/EnrollmentModal.js b/app/components/EnrollmentModal.js
index b2c7ed4..d7be931 100644
--- a/app/components/EnrollmentModal.js
+++ b/app/components/EnrollmentModal.js
@@ -1,199 +1,205 @@
import React, { Component } from 'react';
import { View, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
import PropTypes from 'prop-types';
import superagent from 'superagent';
import autoBind from 'auto-bind';
import { Modal, Portal, Button, TextInput, Title, Surface, HelperText, Snackbar } from 'react-native-paper';
import styles from '../assets/styles/blink/_EnrollmentModal.scss';
import config from '../config';
class EnrollmentModal extends Component {
constructor(props) {
super(props);
autoBind(this);
// save the initial state so we can restore it later
this.initialState = {
yourName: '',
username: '',
password: '',
password2: '',
email: '',
enrolling: false,
error: '',
errorVisible: false
};
this.state = Object.assign({}, this.initialState);
}
handleFormFieldChange(event) {
event.preventDefault();
let state = {};
state[event.target.name] = event.target.value;
this.setState(state);
}
enrollmentFormSubmitted(event) {
event.preventDefault();
// validate the password fields
if (this.state.password !== this.state.password2) {
this.setState({error: 'Password missmatch'});
return;
}
this.setState({enrolling: true, error:''});
superagent.post(config.enrollmentUrl)
.send(superagent.serialize['application/x-www-form-urlencoded']({username: this.state.username,
password: this.state.password,
email: this.state.email,
display_name: this.state.yourName})) //eslint-disable-line camelcase
.end((error, res) => {
this.setState({enrolling: false});
if (error) {
this.setState({error: error.toString(), errorVisible: true});
return;
}
let data;
try {
data = JSON.parse(res.text);
} catch (e) {
this.setState({error: 'Could not decode response data', errorVisible: true});
return;
}
if (data.success) {
this.props.handleEnrollment({accountId: data.sip_address,
password: this.state.password});
this.setState(this.initialState);
} else if (data.error === 'user_exists') {
this.setState({error: 'User already exists', errorVisible: true});
} else {
this.setState({error: data.error_message, errorVisible: true});
}
});
}
onHide() {
this.props.handleEnrollment(null);
this.setState(this.initialState);
}
render() {
let buttonText = 'Create';
let buttonIcon = null;
if (this.state.enrolling) {
buttonIcon = "cog";
}
return (
Create account
@{config.enrollmentDomain}
this.setState({ errorVisible: false })}
-
- >{this.state.error}
+ >
+ {this.state.error}
+
);
}
}
EnrollmentModal.propTypes = {
handleEnrollment: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired
};
export default EnrollmentModal;
diff --git a/app/components/EscalateConferenceModal.js b/app/components/EscalateConferenceModal.js
index 09357aa..1d44618 100644
--- a/app/components/EscalateConferenceModal.js
+++ b/app/components/EscalateConferenceModal.js
@@ -1,64 +1,65 @@
import React from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { Title, Portal, Modal, Paragraph, TextInput, Surface, Button } from 'react-native-paper';
import styles from '../assets/styles/blink/_EscalateConferenceModal.scss';
import config from '../config';
class EscalateConferenceModal extends React.Component {
constructor(props) {
super(props);
this.invitees = React.createRef();
this.escalate = this.escalate.bind(this);
}
escalate(event) {
event.preventDefault();
const uris = [];
for (let item of this.invitees.current.value.split(',')) {
item = item.trim();
if (item.indexOf('@') === -1) {
item = `${item}@${config.defaultDomain}`;
}
uris.push(item);
};
uris.push(this.props.call.remoteIdentity.uri);
this.props.escalateToConference(uris);
}
render() {
return (
Move to conference
Please enter the account(s) you wish to add to this call. After pressing Move, all parties will be invited to join a conference.
);
}
}
EscalateConferenceModal.propTypes = {
show: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
call: PropTypes.object,
escalateToConference: PropTypes.func
};
export default EscalateConferenceModal;
diff --git a/app/components/FooterBox.js b/app/components/FooterBox.js
index e477a82..cb45d0c 100644
--- a/app/components/FooterBox.js
+++ b/app/components/FooterBox.js
@@ -1,18 +1,14 @@
import React from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import styles from '../assets/styles/blink/_Footer.scss';
const FooterBox = () => {
return (
-
-
- Copyright ©AG Projects
-
-
+ © AG Projects
);
};
export default FooterBox
\ No newline at end of file
diff --git a/app/components/HistoryCard.js b/app/components/HistoryCard.js
index 80117f8..aaec372 100644
--- a/app/components/HistoryCard.js
+++ b/app/components/HistoryCard.js
@@ -1,76 +1,76 @@
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import moment from 'moment';
import momentFormat from 'moment-duration-format';
import { Card, IconButton, Headline, Subheading } from 'react-native-paper';
import { Icon } from 'react-native-vector-icons';
import UserIcon from './UserIcon';
const HistoryCard = (props) => {
const classes = props.classes;
const identity = {
displayName: props.historyItem.displayName,
uri: props.historyItem.remoteParty || props.historyItem
}
const startVideoCall = (e) => {
e.stopPropagation();
props.setTargetUri(identity.uri);
// We need to wait for targetURI
setImmediate(() => {
props.startVideoCall(e);
});
}
const startAudioCall = (e) => {
e.stopPropagation();
props.setTargetUri(identity.uri);
// We need to wait for targetURI
setImmediate(() => {
props.startAudioCall(e);
});
}
let duration = moment.duration(props.historyItem.duration, 'seconds').format('hh:mm:ss', {trim: false});
let color = {};
if (props.historyItem.direction === 'received' && props.historyItem.duration === 0) {
color.color = '#a94442';
duration = 'missed';
}
const name = identity.displayName || identity.uri;
return (
{props.setTargetUri(identity.uri)}}
onPress={startVideoCall}
>
{name} ({duration})
-
+
{props.historyItem.startTime}
);
}
HistoryCard.propTypes = {
classes : PropTypes.object.isRequired,
historyItem : PropTypes.object,
startAudioCall : PropTypes.func.isRequired,
startVideoCall : PropTypes.func.isRequired,
setTargetUri : PropTypes.func.isRequired
};
export default HistoryCard;
diff --git a/app/components/InviteParticipantsModal.js b/app/components/InviteParticipantsModal.js
index fec6949..66841de 100644
--- a/app/components/InviteParticipantsModal.js
+++ b/app/components/InviteParticipantsModal.js
@@ -1,55 +1,61 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { Modal, Portal, Text, Button, Surface, TextInput, Title } from 'react-native-paper';
import config from '../config';
class InviteParticipantsModal extends Component {
constructor(props) {
super(props);
this.invitees = React.createRef();
this.invite = this.invite.bind(this);
}
invite(event) {
event.preventDefault();
const uris = [];
this.invitees.current.value.split(',').forEach((item) => {
item = item.trim();
if (item.indexOf('@') === -1) {
item = `${item}@${config.defaultDomain}`;
}
uris.push(item);
});
if (uris && this.props.call) {
this.props.call.inviteParticipants(uris);
}
this.props.close();
}
render() {
return (
Invite Online Users
Enter the users you wish to invite
-
-
+
+
);
}
}
InviteParticipantsModal.propTypes = {
show: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
call: PropTypes.object
};
export default InviteParticipantsModal;
diff --git a/app/components/LocalMedia.js b/app/components/LocalMedia.js
index 8ccb212..f26c889 100644
--- a/app/components/LocalMedia.js
+++ b/app/components/LocalMedia.js
@@ -1,62 +1,61 @@
-import React, { Component } from 'react';
+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 } 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.props.mediaPlaying();
}
hangupCall(event) {
event.preventDefault();
this.props.hangupCall();
}
render() {
let {height, width} = Dimensions.get('window');
let videoStyle = {
height,
width
};
return (
-
+
-
+
-
-
+
);
}
}
LocalMedia.propTypes = {
hangupCall : PropTypes.func,
localMedia : PropTypes.object.isRequired,
mediaPlaying : PropTypes.func.isRequired,
remoteIdentity : PropTypes.string,
generatedVideoTrack : PropTypes.bool
};
export default LocalMedia;
diff --git a/app/components/Logo.js b/app/components/Logo.js
index 68ba2ca..377dd3f 100644
--- a/app/components/Logo.js
+++ b/app/components/Logo.js
@@ -1,20 +1,20 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import { View, Image } from 'react-native';
import { Title } from 'react-native-paper';
import styles from '../assets/styles/blink/_Logo.scss';
const blinkLogo = require('../assets/images/blink-white-big.png');
const Logo = () => {
return (
-
+
Sylk
-
+
);
}
export default Logo;
diff --git a/app/components/NavigationBar.js b/app/components/NavigationBar.js
index b2d06d4..f2ef835 100644
--- a/app/components/NavigationBar.js
+++ b/app/components/NavigationBar.js
@@ -1,110 +1,120 @@
import React, { Component } from 'react';
-import { Linking } from 'react-native';
+import { Linking, Image, View } from 'react-native';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
-import { Appbar, Menu, Divider } from 'react-native-paper';
+import { Appbar, Menu, Divider, Text } from 'react-native-paper';
import config from '../config';
import AboutModal from './AboutModal';
import CallMeMaybeModal from './CallMeMaybeModal';
+import styles from '../assets/styles/blink/_NavigationBar.scss';
+const blinkLogo = require('../assets/images/blink-white-big.png');
class NavigationBar extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
showAboutModal: false,
showCallMeMaybeModal: false,
mute: false,
menuVisible: false
}
this.callUrl = `${config.publicUrl}/call/${props.account.id}`;
this.menuRef = React.createRef();
}
handleMenu(event) {
switch (event) {
case 'about':
this.toggleAboutModal();
break;
case 'callMeMaybe':
this.toggleCallMeMaybeModal();
break;
case 'logOut':
this.props.logout();
break;
case 'preview':
this.props.preview();
break;
case 'settings':
Linking.openURL('https://mdns.sipthor.net/sip_settings.phtml');
break;
default:
break;
}
this.setState({menuVisible: false});
}
toggleMute() {
this.setState(prevState => ({mute: !prevState.mute}));
this.props.toggleMute();
}
toggleAboutModal() {
this.setState({showAboutModal: !this.state.showAboutModal});
}
toggleCallMeMaybeModal() {
this.setState({showCallMeMaybeModal: !this.state.showCallMeMaybeModal});
}
render() {
const muteIcon = this.state.mute ? 'bell-off' : 'bell';
return (
+
);
}
}
NavigationBar.propTypes = {
notificationCenter : PropTypes.func.isRequired,
account : PropTypes.object.isRequired,
logout : PropTypes.func.isRequired,
preview : PropTypes.func.isRequired,
toggleMute : PropTypes.func.isRequired
};
export default NavigationBar;
diff --git a/app/components/Preview.js b/app/components/Preview.js
index 161e533..87b17c9 100644
--- a/app/components/Preview.js
+++ b/app/components/Preview.js
@@ -1,195 +1,193 @@
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}
-
-
+
+
{drawercontent}
);
}
}
Preview.propTypes = {
hangupCall: PropTypes.func,
localMedia: PropTypes.object.isRequired,
setDevice: PropTypes.func.isRequired,
selectedDevices: PropTypes.object.isRequired
};
export default Preview;
diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js
index 8eb5fd9..05755d8 100644
--- a/app/components/ReadyBox.js
+++ b/app/components/ReadyBox.js
@@ -1,151 +1,165 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
// import VizSensor = require('react-visibility-sensor').default;
import autoBind from 'auto-bind';
import { View } from 'react-native';
import { IconButton, Title } from 'react-native-paper';
import ConferenceModal from './ConferenceModal';
import HistoryCard from './HistoryCard';
import HistoryTileBox from './HistoryTileBox';
import FooterBox from './FooterBox';
import URIInput from './URIInput';
import config from '../config';
import utils from '../utils';
import styles from '../assets/styles/blink/_ReadyBox.scss';
class ReadyBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
targetUri: this.props.missedTargetUri,
showConferenceModal: false,
sticky: false,
- height: utils.getWindowHeight() - 120
};
}
getTargetUri() {
const defaultDomain = this.props.account.id.substring(this.props.account.id.indexOf('@') + 1);
return utils.normalizeUri(this.state.targetUri, defaultDomain);
}
handleTargetChange(value) {
this.setState({targetUri: value});
}
handleTargetSelect() {
// the user pressed enter, start a video call by default
if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) {
this.props.startConference(this.state.targetUri);
} else {
this.props.startCall(this.getTargetUri(), {audio: true, video: true});
}
}
handleAudioCall(event) {
event.preventDefault();
if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) {
this.props.startConference(this.state.targetUri);
} else {
this.props.startCall(this.getTargetUri(), {audio: true, video: false});
}
}
handleVideoCall(event) {
event.preventDefault();
if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) {
this.props.startConference(this.state.targetUri);
} else {
this.props.startCall(this.getTargetUri(), {audio: true, video: true});
}
}
showConferenceModal(event) {
event.preventDefault();
if (this.state.targetUri.length !== 0) {
const uri = `${this.state.targetUri.split('@')[0].replace(/[\s()-]/g, '')}@${config.defaultConferenceDomain}`;
this.handleConferenceCall(uri.toLowerCase());
} else {
this.setState({showConferenceModal: true});
}
}
handleConferenceCall(targetUri) {
this.setState({showConferenceModal: false});
if (targetUri) {
this.props.startConference(targetUri);
}
}
- handleResize() {
- this.setState({height: utils.getWindowHeight() - 50});
- }
-
render() {
// Join URIs from local and server history for input
let history = this.props.history.concat(
this.props.serverHistory.map(e => e.remoteParty)
);
history = [...new Set(history)];
-
+ console.log('history from server is', this.props.serverHistory);
return (
-
-
+
+
Enter the address you wish to call
-
+
+
+
-
-
-
+
+
+
{this.props.serverHistory.filter(historyItem => historyItem.remoteParty.startsWith(this.state.targetUri)).map((historyItem, idx) =>
()
)
}
-
+
);
}
}
ReadyBox.propTypes = {
account : PropTypes.object.isRequired,
startCall : PropTypes.func.isRequired,
startConference : PropTypes.func.isRequired,
missedTargetUri : PropTypes.string,
history : PropTypes.array,
serverHistory : PropTypes.array
};
export default ReadyBox;
diff --git a/app/components/RegisterForm.js b/app/components/RegisterForm.js
index 8ccf924..807d64c 100644
--- a/app/components/RegisterForm.js
+++ b/app/components/RegisterForm.js
@@ -1,136 +1,138 @@
import React, { Component } from 'react';
import { View, Keyboard } from 'react-native';
import PropTypes from 'prop-types';
import ipaddr from 'ipaddr.js';
import autoBind from 'auto-bind';
import { Button, TextInput, Title } from 'react-native-paper';
import EnrollmentModal from './EnrollmentModal';
import storage from '../storage';
import styles from '../assets/styles/blink/_RegisterForm.scss';
class RegisterForm extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
accountId: '',
password: '',
registering: false,
remember: false,
showEnrollmentModal: false
};
}
componentDidMount() {
storage.get('account').then((account) => {
if (account) {
this.setState(Object.assign({}, account, {remember: true}));
if (this.props.autoLogin && this.state.password !== '') {
this.props.handleRegistration(this.state.accountId, this.state.password);
}
}
});
}
handleAccountIdChange(value) {
this.setState({accountId: value});
}
handlePasswordChange(value) {
this.setState({password: value});
}
handleSubmit(event) {
if (event) {
event.preventDefault();
}
Keyboard.dismiss();
this.props.handleRegistration(this.state.accountId, this.state.password, true);
}
handleEnrollment(account) {
this.setState({showEnrollmentModal: false});
if (account !== null) {
this.setState({accountId: account.accountId, password: account.password, registering: true});
this.props.handleRegistration(account.accountId, account.password);
}
}
createAccount(event) {
event.preventDefault();
this.setState({showEnrollmentModal: true});
}
render() {
const domain = this.state.accountId.substring(this.state.accountId.indexOf('@') + 1);
const validDomain = !ipaddr.IPv4.isValidFourPartDecimal(domain) && !ipaddr.IPv6.isValid(domain);
const validInput = validDomain && this.state.accountId.indexOf('@') !== -1 && this.state.password !== 0;
return (
Sign in to continue
);
}
}
RegisterForm.propTypes = {
classes : PropTypes.object,
handleRegistration : PropTypes.func.isRequired,
registrationInProgress : PropTypes.bool.isRequired,
autoLogin : PropTypes.bool
};
export default RegisterForm;
\ No newline at end of file
diff --git a/app/components/StatusBox.js b/app/components/StatusBox.js
index 4229f94..3fc7cbb 100644
--- a/app/components/StatusBox.js
+++ b/app/components/StatusBox.js
@@ -1,38 +1,36 @@
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import { Snackbar } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const StatusBox = (props) => {
let iconName;
switch (props.level) {
case 'info':
iconName = 'information-outline';
break;
case 'danger':
iconName = 'alert-circle-outline';
break;
case 'warning':
iconName = 'alert-octogon-outline';
break;
}
return (
-
- {}}>
- { iconName ? () : null }{ props.title }{ props.message }
-
-
+ {}}>
+ { iconName ? () : null }{ props.title }{ props.message }
+
);
};
StatusBox.propTypes = {
level: PropTypes.string,
message: PropTypes.string.isRequired,
title: PropTypes.string,
};
export default StatusBox;
diff --git a/app/components/URIInput.js b/app/components/URIInput.js
index 461d7bb..1e0565c 100644
--- a/app/components/URIInput.js
+++ b/app/components/URIInput.js
@@ -1,129 +1,128 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { View } from 'react-native';
import { TextInput } from 'react-native-paper';
import autoBind from 'auto-bind';
class URIInput extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
selecting: false
};
this.uriInput = React.createRef();
this.clicked = false;
this.autoComplete;
}
componentDidMount() {
// this.autoComplete = autocomplete('#uri-input', { hint: false }, [
// {
// source: (query, cb) => {
// let data = this.props.data.filter((item) => {
// return item.startsWith(query);
// });
// cb(data);
// },
// displayKey: String,
// templates: {
// suggestion: (suggestion) => {
// return suggestion;
// }
// }
// }
// ]).on('autocomplete:selected', (event, suggestion, dataset) => {
// this.setValue(suggestion);
// });
if (this.props.autoFocus) {
this.uriInput.current.focus();
}
}
componentDidUpdate(prevProps) {
if (prevProps.defaultValue !== this.props.defaultValue && this.props.autoFocus) {
this.uriInput.current.focus();
}
}
setValue(value) {
this.props.onChange(value);
}
onInputChange(value) {
this.setValue(value);
}
onInputClick(event) {
if (!this.clicked) {
this.uriInput.current.select();
this.clicked = true;
}
}
onInputKeyDown(event) {
switch (event.which) {
case 13:
// ENTER
if (this.state.selecting) {
this.setState({selecting: false});
} else {
this.props.onSelect(event.target.value);
}
break;
case 27:
// ESC
this.setState({selecting: false});
break;
case 38:
case 40:
// UP / DOWN ARROW
this.setState({selecting: true});
break;
default:
break;
}
}
onInputBlur(event) {
// focus was lost, reset selecting state
if (this.state.selecting) {
this.setState({selecting: false});
}
this.clicked = false;
}
render() {
return (
-
-
-
+
);
}
}
URIInput.propTypes = {
defaultValue: PropTypes.string.isRequired,
data: PropTypes.array.isRequired,
autoFocus: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
placeholder : PropTypes.string
};
export default URIInput;
diff --git a/app/components/VideoBox.js b/app/components/VideoBox.js
index c5d619d..495ef51 100644
--- a/app/components/VideoBox.js
+++ b/app/components/VideoBox.js
@@ -1,303 +1,339 @@
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 } from 'react-native';
import { RTCView } from 'react-native-webrtc';
import CallOverlay from './CallOverlay';
import EscalateConferenceModal from './EscalateConferenceModal';
+import DTMFModal from './DTMFModal';
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
+ remoteStream: null,
+ showDtmfModal: 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();
}
}
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.setState({audioMuted: false});
} else {
DEBUG('Mute microphone');
track.enabled = false;
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});
}
}
}
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 ();
+ 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 = (
//
//
//
// );
}
- let {height, width} = Dimensions.get('window');
- let videoStyle = {
- height,
- width
- };
-
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';
return (
{/*onMouseMove={this.showCallOverlay}*/}
{/* */}
{/* {watermark} */}
{/* */}
{this.state.remoteVideoShow ?
-
+
: null }
{ this.state.localVideoShow ?
-
+
: null }
-
-
-
-
+
+
+
+
+
+
);
}
}
VideoBox.propTypes = {
call : PropTypes.object,
localMedia : PropTypes.object,
hangupCall : PropTypes.func,
shareScreen : PropTypes.func,
escalateToConference : PropTypes.func,
generatedVideoTrack : PropTypes.bool
};
export default VideoBox;