diff --git a/app/assets/styles/blink/_AboutModal.scss b/app/assets/styles/blink/_AboutModal.scss
index d1672bc..6bee68a 100644
--- a/app/assets/styles/blink/_AboutModal.scss
+++ b/app/assets/styles/blink/_AboutModal.scss
@@ -1,4 +1,16 @@
+.title {
+ padding: 5px;
+ font-size: 36px;
+ text-align: center;
+}
+
+.body {
+ padding: 10px;
+ font-size: 16px;
+ text-align: center;
+}
+
.container {
- padding: 30px;
+ padding: 40px;
margin: 10px;
-}
\ No newline at end of file
+}
diff --git a/app/assets/styles/blink/_AudioCallBox.scss b/app/assets/styles/blink/_AudioCallBox.scss
index d72c7db..6d5bd30 100644
--- a/app/assets/styles/blink/_AudioCallBox.scss
+++ b/app/assets/styles/blink/_AudioCallBox.scss
@@ -1,26 +1,28 @@
.container {
+}
+.callStatus {
}
.buttonContainer {
flex-direction: row;
margin: 0 auto;
- padding-top: 10px;
+ padding-top: 20px;
}
.button {
background-color: white;
margin: 10px;
+ padding-top: 5px;
+ padding-left: 0px;
}
.hangupButton {
- background-color: rgba(#a94442, .8);
+ background-color: rgba(#a94442, .9);
}
.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 95ad425..feb5bfc 100644
--- a/app/assets/styles/blink/_CallMeMaybeModal.scss
+++ b/app/assets/styles/blink/_CallMeMaybeModal.scss
@@ -1,8 +1,20 @@
+.title {
+ padding: 5px;
+ font-size: 36px;
+ text-align: center;
+}
+
+.body {
+ padding: 10px;
+ font-size: 16px;
+ text-align: center;
+}
+
.container {
padding: 30px;
margin: 10px;
}
.iconContainer {
flex-direction: row;
-}
\ No newline at end of file
+}
diff --git a/app/assets/styles/blink/_ConferenceModal.scss b/app/assets/styles/blink/_ConferenceModal.scss
index 8efcb51..3521125 100644
--- a/app/assets/styles/blink/_ConferenceModal.scss
+++ b/app/assets/styles/blink/_ConferenceModal.scss
@@ -1,4 +1,16 @@
.container {
padding: 30px;
margin: 30px;
-}
\ No newline at end of file
+}
+
+.title {
+ padding: 5px;
+ font-size: 20px;
+ text-align: center;
+}
+
+.body {
+ padding: 10px;
+ font-size: 16px;
+ text-align: center;
+}
diff --git a/app/assets/styles/blink/_DTMFModal.scss b/app/assets/styles/blink/_DTMFModal.scss
index f938176..ae9759a 100644
--- a/app/assets/styles/blink/_DTMFModal.scss
+++ b/app/assets/styles/blink/_DTMFModal.scss
@@ -1,13 +1,13 @@
-.conatiner {
+.container {
}
.row {
flex-direction: row;
margin: 0 auto;
padding-top: 10px;
}
.button {
}
diff --git a/app/assets/styles/blink/_EnrollmentModal.scss b/app/assets/styles/blink/_EnrollmentModal.scss
index 54c95e5..00e2dae 100644
--- a/app/assets/styles/blink/_EnrollmentModal.scss
+++ b/app/assets/styles/blink/_EnrollmentModal.scss
@@ -1,15 +1,15 @@
.title {
color: black;
margin: 0 auto;
}
.container {
- padding: 30px;
+ padding: 10px;
//flex: 1;
justify-content: flex-start;
}
.inner {
// flex: 1;
// justify-content: flex-end;
-}
\ No newline at end of file
+}
diff --git a/app/assets/styles/blink/_Footer.scss b/app/assets/styles/blink/_Footer.scss
index 34db4cf..e3f18c6 100644
--- a/app/assets/styles/blink/_Footer.scss
+++ b/app/assets/styles/blink/_Footer.scss
@@ -1,7 +1,7 @@
.container {
margin: 0 auto;
}
.text {
color: white;
-}
\ No newline at end of file
+}
diff --git a/app/assets/styles/blink/_IncomingCallModal.scss b/app/assets/styles/blink/_IncomingCallModal.scss
index bbfe90f..8d04f88 100644
--- a/app/assets/styles/blink/_IncomingCallModal.scss
+++ b/app/assets/styles/blink/_IncomingCallModal.scss
@@ -1,11 +1,41 @@
.container {
+ justifyContent: center;
+ alignItems: center;
+ background-color: rgba(#ccc, .9);
+ padding-top: 200px;
+ padding-bottom: 200px;
+}
+.buttonContainer {
+ flex-direction: row;
+ margin: 0 auto;
+ padding-top: 20px;
}
.button {
+ background-color: rgba(#6DAA63, .9);
+ margin: 10px;
+ padding-top: 4px;
+ padding-left: 1px;
+}
+.rejectButton {
+ background-color: rgba(#a94442, .9);
+ margin: 10px;
+ padding-top: 4px;
+ padding-left: 1px;
}
-.buttonContainer {
+.userIcon {
+ margin: 0;
+ padding-bottom: 20px;
+}
+
+.remoteCaller {
+ padding-top: 20px;
+ font-size: 34px;
+ margin: 0px;
+}
-}
\ No newline at end of file
+.remoteMedia {
+}
diff --git a/app/assets/styles/blink/_LocalMedia.scss b/app/assets/styles/blink/_LocalMedia.scss
index 5ee57de..49c5190 100644
--- a/app/assets/styles/blink/_LocalMedia.scss
+++ b/app/assets/styles/blink/_LocalMedia.scss
@@ -1,22 +1,22 @@
.container {
flex: 1;
height: 100%;
width: 100%;
}
.video {
}
.buttonContainer {
position: absolute;
- bottom: 100;
+ bottom: 40;
width: 100%;
z-index: 99;
justify-content: center;
align-items: center;
}
.button {
background-color: rgba(#a94442, .8);
}
diff --git a/app/assets/styles/blink/_Logo.scss b/app/assets/styles/blink/_Logo.scss
index b113b45..1a13ae7 100644
--- a/app/assets/styles/blink/_Logo.scss
+++ b/app/assets/styles/blink/_Logo.scss
@@ -1,14 +1,18 @@
.title {
+ padding: 5px;
margin: 0 auto;
color: white;
+ font-size: 36px;
}
.logoContainer {
- margin: 0 auto;
+ margin:0 auto;
}
.logo {
- width: 200px;
- height: 200px;
+ width: 120px;
+ height: 120px;
resize-mode: contain;
-}
\ No newline at end of file
+ margin-top: 10px;
+ margin-bottom: 20px;
+}
diff --git a/app/assets/styles/blink/_NavigationBar.scss b/app/assets/styles/blink/_NavigationBar.scss
index 57829bd..043f790 100644
--- a/app/assets/styles/blink/_NavigationBar.scss
+++ b/app/assets/styles/blink/_NavigationBar.scss
@@ -1,12 +1,14 @@
.titleContainer {
-
}
.title {
color: white;
}
.logo {
- height: 50px;
- width: 50px;
-}
\ No newline at end of file
+ margin-top: 0px;
+ margin-left: 0px;
+ margin-right: 0px;
+ height: 40px;
+ width: 40px;
+}
diff --git a/app/assets/styles/blink/_Preview.scss b/app/assets/styles/blink/_Preview.scss
index 98cdebf..fb7e9d1 100644
--- a/app/assets/styles/blink/_Preview.scss
+++ b/app/assets/styles/blink/_Preview.scss
@@ -1,29 +1,29 @@
.container {
flex: 1;
}
.videoContainer {
width: 100%;
height: 100%;
}
.video {
height: 100%;
width: 100%;
}
.buttonContainer {
position: absolute;
- bottom: 100;
+ bottom: 40;
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 daca1ef..b423d02 100644
--- a/app/assets/styles/blink/_ReadyBox.scss
+++ b/app/assets/styles/blink/_ReadyBox.scss
@@ -1,38 +1,46 @@
@import './variables';
.wholeContainer {
flex: 1;
flex-direction: column;
justify-content: center;
}
.container {
margin: 0 auto;
- width: 80%;
- padding-top: 30px;
+ width: 90%;
+ padding-top: 5%;
}
.title {
color: white;
+ font-size: 20px;
}
.footer {
flex: 1;
justify-content: flex-end;
padding-bottom: 15px;
}
.button {
- background-color: grey;
+ background-color: rgba(#6DAA63, .9);
+ margin: 10px;
+ padding-top: 2px;
+ padding-left: 1px;
+}
+
+.conferenceButton {
+ background-color: rgba(#4572a6, 1);
margin: 10px;
}
.buttonGroup {
flex-direction: row;
margin: 0 auto;
padding-top: 10px;
}
.uriInputBox {
padding-top: 10px;
}
diff --git a/app/assets/styles/blink/_RegisterBox.scss b/app/assets/styles/blink/_RegisterBox.scss
index 0e39b5d..2064cc9 100644
--- a/app/assets/styles/blink/_RegisterBox.scss
+++ b/app/assets/styles/blink/_RegisterBox.scss
@@ -1,7 +1,7 @@
.registerBox {
padding: 30px;
}
.title {
color: white;
-}
\ No newline at end of file
+}
diff --git a/app/assets/styles/blink/_RegisterForm.scss b/app/assets/styles/blink/_RegisterForm.scss
index b1790c0..4f03a82 100644
--- a/app/assets/styles/blink/_RegisterForm.scss
+++ b/app/assets/styles/blink/_RegisterForm.scss
@@ -1,17 +1,22 @@
.title {
margin: 0 auto;
color: white;
+ margin-bottom: 10px;
+ font-size: 18px;
}
.row {
- padding: 12px 0;
+ padding: 0px 0;
}
.button {
- margin: 4px;
+ margin-top: 30px;
+ margin-left: 60px;
+ margin-right: 60px;
+ border-radius: 20px;
}
.input {
- border-radius: 5px;
+ border-radius: 0px;
}
diff --git a/app/assets/styles/blink/_VideoBox.scss b/app/assets/styles/blink/_VideoBox.scss
index bf5b945..85afc23 100644
--- a/app/assets/styles/blink/_VideoBox.scss
+++ b/app/assets/styles/blink/_VideoBox.scss
@@ -1,43 +1,49 @@
.container {
flex: 1;
}
.remoteVideoContainer {
position: absolute;
left: 0;
right: 0;
- top: 0;
+ top: 50;
bottom: 0;
}
.localVideoContainer {
justify-content: flex-end;
}
.video {
width: 100%;
height: 100%;
object-fit: cover;
background-color: red;
}
.localVideo {
- height: 100px;
+ position: absolute;
+ height: 80px;
width: 100px;
object-fit: cover;
background-color: white;
+ top: 10;
+ left: 10;
+ border-radius: 10px;
}
.buttonContainer {
flex-direction: row;
margin: 0 auto;
+ bottom: 30;
}
.button {
- background-color: white;
+ background-color: rgba(#F9F9F9, .7);
margin: 10px;
+ padding-top: 5px;
}
.hangupButton {
- background-color: rgba(#a94442, .8);
-}
\ No newline at end of file
+ background-color: rgba(#a94442, .5);
+}
diff --git a/app/assets/styles/blink/_call.scss b/app/assets/styles/blink/_call.scss
index d79f4d7..b7b4272 100644
--- a/app/assets/styles/blink/_call.scss
+++ b/app/assets/styles/blink/_call.scss
@@ -1,74 +1,74 @@
.call-buttons {
position: absolute;
right: 0;
bottom: 0 !important;
- left: 0;
+ left: 00;
z-index: 1;
padding-bottom: 35px;
margin: auto;
overflow: hidden;
}
.top-overlay {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 2;
}
.call-header {
padding: 4px 0;
color: $white;
background-color: $darker-gray-transparent;
p {
margin-bottom: 2px !important;
overflow: hidden;
font-size: 17px !important;
line-height: 1.2 !important;
text-overflow: ellipsis;
}
p + p {
margin-bottom: 0 !important;
}
}
.call-user-icon {
padding-bottom: 100px;
}
// Buttons Animation
.videobuttons-enter {
animation: fadeInUp linear .3s;
}
.videobuttons-exit {
&.videobuttons-exit-active {
animation: fadeOutDown linear .3s;
}
}
// Video header animation
.videoheader-enter {
animation: fadeInDown linear .3s;
}
.videoheader-exit {
&.videoheader-exit-active {
animation: fadeOutUp linear .3s;
}
}
// Watermark animation
.watermark-enter {
animation: fadeIn linear .6s;
}
.watermark-exit {
&.watermark-exit-active {
animation: fadeOut linear .3s;
}
}
diff --git a/app/components/AboutModal.js b/app/components/AboutModal.js
index 3a170ee..e7edcbd 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
- © AG Projects
+ About Sylk
+ Sylk is the WebRTC client companion for SylkServer
+ http://sylkserver.com
);
}
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 8c63df1..1a33515 100644
--- a/app/components/AudioCallBox.js
+++ b/app/components/AudioCallBox.js
@@ -1,204 +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 (
-
-
-
+
+
);
}
}
AudioCallBox.propTypes = {
call : PropTypes.object,
escalateToConference : PropTypes.func,
hangupCall : PropTypes.func,
mediaPlaying : PropTypes.func,
remoteIdentity : PropTypes.string
};
export default AudioCallBox;
diff --git a/app/components/CallMeMaybeModal.js b/app/components/CallMeMaybeModal.js
index b228ea2..8ddf791 100644
--- a/app/components/CallMeMaybeModal.js
+++ b/app/components/CallMeMaybeModal.js
@@ -1,80 +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.notificationCenter().postSystemNotification('Call me', {body: 'Web address 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.notificationCenter().postSystemNotification('Call me', {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.
+ Call me, maybe?
+
+ Share {this.props.callUrl} with others so they can easily call you.
);
}
}
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 386306a..f463778 100644
--- a/app/components/CallOverlay.js
+++ b/app/components/CallOverlay.js
@@ -1,105 +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 = null;
if (this.props.show) {
let callDetail;
if (this.duration !== null) {
callDetail = {this.duration};
- callDetail = this.duration;
+ callDetail = 'Duration:' + this.duration;
} else {
callDetail = 'Connecting...'
}
header = (
);
}
return header
}
}
CallOverlay.propTypes = {
show: PropTypes.bool.isRequired,
remoteIdentity: PropTypes.string.isRequired,
call: PropTypes.object
};
export default CallOverlay;
diff --git a/app/components/ConferenceBox.js b/app/components/ConferenceBox.js
index 9068248..8bfbbd3 100644
--- a/app/components/ConferenceBox.js
+++ b/app/components/ConferenceBox.js
@@ -1,687 +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://')) {
+ //if (window.location.origin.startsWith('file://')) {
this.callUrl = `${config.publicUrl}/conference/${friendlyName}`;
- } else {
- this.callUrl = `${window.location.origin}/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(
);
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;
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 {
this.state.participants.forEach((p) => {
videos.push(
);
drawerParticipants.push(
);
});
}
}
let filesDrawerContent = (
);
return (
{videos}
{participants}
{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/ConferenceModal.js b/app/components/ConferenceModal.js
index e16f7d5..418e749 100644
--- a/app/components/ConferenceModal.js
+++ b/app/components/ConferenceModal.js
@@ -1,77 +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
+ Join Conference
+ Enter the 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/DTMFModal.js b/app/components/DTMFModal.js
index bdabc5c..bfcba07 100644
--- a/app/components/DTMFModal.js
+++ b/app/components/DTMFModal.js
@@ -1,67 +1,67 @@
import debug from 'debug';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
-import { Portal, Modal, Surface, Title, Button } from 'react-native-paper';
+import { Portal, Modal, Surface, Title, Button, Text} from 'react-native-paper';
import dtmf from 'react-native-dtmf';
import styles from '../assets/styles/blink/_DTMFModal.scss';
const DEBUG = debug('blinkrtc:DTMF');
debug.enable('*');
class DTMFModal extends Component {
sendDtmf(tone) {
DEBUG('DTMF tone was sent: ' + tone);
dtmf.stopTone();//don't play a tone at the same time as another
dtmf.playTone(dtmf['DTMF_' + tone], 500);
if (this.props.call !== null && this.props.call.state === 'established') {
this.props.call.sendDtmf(tone);
}
}
render() {
return (
- DTMF
+ DTMF
);
}
}
DTMFModal.propTypes = {
show: PropTypes.bool.isRequired,
hide: PropTypes.func.isRequired,
call: PropTypes.object
};
export default DTMFModal;
diff --git a/app/components/EnrollmentModal.js b/app/components/EnrollmentModal.js
index d7be931..87c1ec9 100644
--- a/app/components/EnrollmentModal.js
+++ b/app/components/EnrollmentModal.js
@@ -1,205 +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}
);
}
}
EnrollmentModal.propTypes = {
handleEnrollment: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired
};
export default EnrollmentModal;
diff --git a/app/components/FooterBox.js b/app/components/FooterBox.js
index cb45d0c..ffa2e8d 100644
--- a/app/components/FooterBox.js
+++ b/app/components/FooterBox.js
@@ -1,14 +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 (
© AG Projects
);
};
-export default FooterBox
\ No newline at end of file
+export default FooterBox
diff --git a/app/components/IncomingCallModal.js b/app/components/IncomingCallModal.js
index d56072f..bf52535 100644
--- a/app/components/IncomingCallModal.js
+++ b/app/components/IncomingCallModal.js
@@ -1,66 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import UserIcon from './UserIcon';
import { Headline, IconButton, Title, Portal, Modal, Surface } from 'react-native-paper';
import { View } from 'react-native';
import styles from '../assets/styles/blink/_IncomingCallModal.scss';
const IncomingCallModal = (props) => {
const answerAudioOnly = () => {
props.onAnswer({audio: true, video: false});
}
const answer = () => {
props.onAnswer({audio: true, video: true});
};
if (props.call == null) {
return false;
}
- let answerButtons = [
-
- ];
+ let answerButtons = [];
+
+ answerButtons.push(
+
+ );
let callType = 'audio';
if (props.call.mediaTypes.video) {
callType = 'video';
answerButtons.push(
-
+
);
}
answerButtons.push(
-
+
);
const remoteIdentityLine = props.call.remoteIdentity.displayName || props.call.remoteIdentity.uri;
return (
-
- {remoteIdentityLine}
- is calling with {callType}
+
+ {remoteIdentityLine}
+ is calling with {callType}
{answerButtons}
);
}
IncomingCallModal.propTypes = {
call : PropTypes.object,
onAnswer : PropTypes.func.isRequired,
onHangup : PropTypes.func.isRequired,
compact : PropTypes.bool,
show : PropTypes.bool
};
export default IncomingCallModal;
diff --git a/app/components/LocalMedia.js b/app/components/LocalMedia.js
index f26c889..3311918 100644
--- a/app/components/LocalMedia.js
+++ b/app/components/LocalMedia.js
@@ -1,61 +1,61 @@
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/NavigationBar.js b/app/components/NavigationBar.js
index f2ef835..2a604ac 100644
--- a/app/components/NavigationBar.js
+++ b/app/components/NavigationBar.js
@@ -1,120 +1,119 @@
import React, { Component } from 'react';
import { Linking, Image, View } from 'react-native';
import PropTypes from 'prop-types';
import autoBind from 'auto-bind';
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');
+ Linking.openURL(config.serverSettingsUrl);
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 87b17c9..50fcb95 100644
--- a/app/components/Preview.js
+++ b/app/components/Preview.js
@@ -1,193 +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}
);
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 7b698e4..9406b29 100644
--- a/app/components/ReadyBox.js
+++ b/app/components/ReadyBox.js
@@ -1,164 +1,164 @@
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,
};
}
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);
}
}
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
+ Enter address or phone number
{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 807d64c..eedaf89 100644
--- a/app/components/RegisterForm.js
+++ b/app/components/RegisterForm.js
@@ -1,138 +1,143 @@
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 config from '../config';
+
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
+
+ { config.enrollmentUrl ?
+ : null }
);
}
}
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
+export default RegisterForm;
diff --git a/app/components/URIInput.js b/app/components/URIInput.js
index 1e0565c..2b841d9 100644
--- a/app/components/URIInput.js
+++ b/app/components/URIInput.js
@@ -1,128 +1,129 @@
import React from 'react';
import PropTypes from 'prop-types';
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 495ef51..d7a00ac 100644
--- a/app/components/VideoBox.js
+++ b/app/components/VideoBox.js
@@ -1,339 +1,370 @@
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 config from '../config';
+import dtmf from 'react-native-dtmf';
import styles from '../assets/styles/blink/_VideoBox.scss';
const DEBUG = debug('blinkrtc:Video');
debug.enable('*');
class VideoBox extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
callOverlayVisible: true,
audioMuted: false,
videoMuted: false,
localVideoShow: false,
remoteVideoShow: false,
remoteSharesScreen: false,
showEscalateConferenceModal: false,
localStream: null,
remoteStream: null,
- showDtmfModal: false
+ showDtmfModal: false,
+ doorOpened: false
};
this.overlayTimer = null;
this.localVideo = React.createRef();
this.remoteVideo = React.createRef();
}
callStateChanged(oldState, newState, data) {
DEBUG(`Call state changed ${oldState} -> ${newState}`);
if (newState === 'established') {
this.forceUpdate();
}
}
+ openDoor() {
+ const tone = config.intercomDtmfTone;
+ DEBUG('DTMF tone sent to intercom: ' + tone);
+ this.setState({doorOpened: true});
+ this.forceUpdate();
+
+ dtmf.stopTone(); //don't play a tone at the same time as another
+ dtmf.playTone(dtmf['DTMF_' + tone], 1000);
+
+ if (this.props.call !== null && this.props.call.state === 'established') {
+ this.props.call.sendDtmf(tone);
+ /*this.props.notificationCenter.postSystemNotification('Door opened', {timeout: 5});*/
+ }
+ }
+
componentDidMount() {
console.log('localStreams', this.props.call.getLocalStreams());
console.log('remoteStreams', this.props.call.getRemoteStreams());
this.setState({localStream: this.props.call.getLocalStreams()[0], localVideoShow: true, remoteStream: this.props.call.getRemoteStreams()[0], remoteVideoShow: true});
this.props.call.on('stateChanged', this.callStateChanged);
+
// sylkrtc.utils.attachMediaStream(, this.localVideo.current, {disableContextMenu: true});
// let promise = this.localVideo.current.play()
// if (promise !== undefined) {
// promise.then(_ => {
// this.setState({localVideoShow: true}); // eslint-disable-line react/no-did-mount-set-state
// // Autoplay started!
// }).catch(error => {
// // Autoplay was prevented.
// // Show a "Play" button so that user can start playback.
// });
// } else {
// this.localVideo.current.addEventListener('playing', () => {
// this.setState({}); // eslint-disable-line react/no-did-mount-set-state
// });
// }
// this.remoteVideo.current.addEventListener('playing', this.handleRemoteVideoPlaying);
// sylkrtc.utils.attachMediaStream(this.props.call.getRemoteStreams()[0], this.remoteVideo.current, {disableContextMenu: true});
}
componentWillUnmount() {
// clearTimeout(this.overlayTimer);
// this.remoteVideo.current.removeEventListener('playing', this.handleRemoteVideoPlaying);
// this.exitFullscreen();
}
showDtmfModal() {
this.setState({showDtmfModal: true});
}
hideDtmfModal() {
this.setState({showDtmfModal: false});
}
handleFullscreen(event) {
event.preventDefault();
// this.toggleFullscreen();
}
handleRemoteVideoPlaying() {
this.setState({remoteVideoShow: true});
// this.remoteVideo.current.onresize = (event) => {
// this.handleRemoteResize(event)
// };
// this.armOverlayTimer();
}
handleRemoteResize(event, target) {
//DEBUG("%o", event);
const resolutions = [ '1280x720', '960x540', '640x480', '640x360', '480x270','320x180'];
const videoResolution = event.target.videoWidth + 'x' + event.target.videoHeight;
if (resolutions.indexOf(videoResolution) === -1) {
this.setState({remoteSharesScreen: true});
} else {
this.setState({remoteSharesScreen: false});
}
}
muteAudio(event) {
event.preventDefault();
const localStream = this.state.localStream;
if (localStream.getAudioTracks().length > 0) {
const track = localStream.getAudioTracks()[0];
if(this.state.audioMuted) {
DEBUG('Unmute microphone');
track.enabled = true;
this.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 null;
}
const localVideoClasses = classNames({
'video-thumbnail' : true,
'mirror' : !this.props.call.sharingScreen && !this.props.generatedVideoTrack,
'hidden' : !this.state.localVideoShow,
'animated' : true,
'fadeIn' : this.state.localVideoShow || this.state.videoMuted,
'fadeOut' : this.state.videoMuted,
'fit' : this.props.call.sharingScreen
});
const remoteVideoClasses = classNames({
'poster' : !this.state.remoteVideoShow,
'animated' : true,
'fadeIn' : this.state.remoteVideoShow,
'large' : true,
'fit' : this.state.remoteSharesScreen
});
let callButtons;
let watermark;
if (this.state.callOverlayVisible) {
// const screenSharingButtonIcons = classNames({
// 'fa' : true,
// 'fa-clone' : true,
// 'fa-flip-horizontal' : true,
// 'text-warning' : this.props.call.sharingScreen
// });
// const fullScreenButtonIcons = classNames({
// 'fa' : true,
// 'fa-expand' : !this.isFullScreen(),
// 'fa-compress' : this.isFullScreen()
// });
// const commonButtonClasses = classNames({
// 'btn' : true,
// 'btn-round' : true,
// 'btn-default' : true
// });
const buttons = [];
// buttons.push();
// if (this.isFullscreenSupported()) {
// buttons.push();
// }
// buttons.push(
);
// callButtons = (
// //
// //
// );
} else {
// watermark = (
//
//
//
// );
}
console.log('local media stream in videobox', this.state);
const muteButtonIcons = this.state.audioMuted ? 'microphone-off' : 'microphone';
const muteVideoButtonIcons = this.state.videoMuted ? 'video-off' : 'video';
return (
{/*onMouseMove={this.showCallOverlay}*/}
{/* */}
{/* {watermark} */}
{/* */}
{this.state.remoteVideoShow ?
: null }
{ this.state.localVideoShow ?
: null }
+ { config.intercomDtmfTone ?
+
+
+
+
+ :
-
+
+ }
);
}
}
VideoBox.propTypes = {
call : PropTypes.object,
localMedia : PropTypes.object,
hangupCall : PropTypes.func,
shareScreen : PropTypes.func,
escalateToConference : PropTypes.func,
generatedVideoTrack : PropTypes.bool
};
export default VideoBox;
diff --git a/app/config.js b/app/config.js
index b2af8ef..be98921 100644
--- a/app/config.js
+++ b/app/config.js
@@ -1,20 +1,22 @@
'use strict';
const defaultDomain = 'sip2sip.info';
const configOptions = {
defaultDomain : defaultDomain,
enrollmentDomain : defaultDomain,
- publicUrl : 'https://webrtc.sipthor.net',
- enrollmentUrl : 'https://blink.sipthor.net/enrollment-webrtc.phtml',
- useServerCallHistory : true,
- serverCallHistoryUrl : 'https://blink.sipthor.net/settings-webrtc.phtml',
defaultConferenceDomain : `videoconference.${defaultDomain}`,
defaultGuestDomain : `guest.${defaultDomain}`,
wsServer : 'wss://webrtc-gateway.sipthor.net:9999/webrtcgateway/ws',
+ publicUrl : 'https://webrtc.sipthor.net',
+ enrollmentUrl : 'https://blink.sipthor.net/enrollment-webrtc.phtml',
+ serverCallHistoryUrl : 'https://blink.sipthor.net/settings-webrtc.phtml',
+ serverSettingsUrl : 'https://mdns.sipthor.net/sip_settings.phtml',
fileSharingUrl : 'https://webrtc-gateway.sipthor.net:9999/webrtcgateway/filesharing',
- iceServers : [{urls: 'stun:stun.sipthor.net:3478'}]
+ iceServers : [{urls: 'stun:stun.sipthor.net:3478'}],
+ useServerCallHistory : true,
+ intercomDtmfTone : true
};
module.exports = configOptions;