diff --git a/Logger.js b/Logger.js
index 4e5ca40..68406b8 100644
--- a/Logger.js
+++ b/Logger.js
@@ -1,38 +1,37 @@
import debug from 'react-native-debug';
-import util from 'util';
-debug.enable('*');
+//debug.enable('*');
-const APP_NAME = 'blinkrtc';
+const APP_NAME = 'sylk';
export default class Logger {
constructor(prefix) {
if (prefix) {
this._debug = debug(APP_NAME + ':' + prefix);
this._warn = debug(APP_NAME + ':WARN:' + prefix);
this._error = debug(APP_NAME + ':ERROR:' + prefix);
}
else {
this._debug = debug(APP_NAME);
this._warn = debug(APP_NAME + ':WARN');
this._error = debug(APP_NAME + ':ERROR');
}
this._debug.log = console.info.bind(console);
this._warn.log = console.warn.bind(console);
this._error.log = console.error.bind(console);
}
get debug() {
return this._debug;
}
get warn() {
return this._warn;
}
get error() {
return this._error;
}
}
diff --git a/app/components/Call.js b/app/components/Call.js
index 7cad547..2d20d00 100644
--- a/app/components/Call.js
+++ b/app/components/Call.js
@@ -1,181 +1,181 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import assert from 'assert';
import debug from 'react-native-debug';
import autoBind from 'auto-bind';
import Logger from "../../Logger";
import AudioCallBox from './AudioCallBox';
import LocalMedia from './LocalMedia';
import VideoBox from './VideoBox';
import config from '../config';
const logger = new Logger("Call");
class Call extends Component {
constructor(props) {
super(props);
autoBind(this);
if (this.props.localMedia && this.props.localMedia.getVideoTracks().length === 0) {
logger.debug('Will send audio only');
this.state = {audioOnly: true};
} else {
this.state = {audioOnly: false};
}
// If current call is available on mount we must have incoming
if (this.props.currentCall != null) {
this.props.currentCall.on('stateChanged', this.callStateChanged);
}
}
componentWillReceiveProps(nextProps) {
// Needed for switching to incoming call while in a call
if (this.props.currentCall != null && this.props.currentCall != nextProps.currentCall) {
if (nextProps.currentCall != null) {
nextProps.currentCall.on('stateChanged', this.callStateChanged);
} else {
this.props.currentCall.removeListener('stateChanged', this.callStateChanged);
}
}
}
callStateChanged(oldState, newState, data) {
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) {
- logger.debug('Media type changed to audio');
+ console.log('Media type changed to audio');
// Stop local video
if (this.props.localMedia.getVideoTracks().length !== 0) {
currentCall.getLocalStreams()[0].getVideoTracks()[0].stop();
}
this.setState({audioOnly: true});
} 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');
+ console.log('Media type changed to video on accepted');
this.setState({audioOnly: false});
}
}
}
call() {
assert(this.props.currentCall === null, 'currentCall is not null');
let options = {pcConfig: {iceServers: config.iceServers}};
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.call();
} else {
this.answerCall();
}
}
render() {
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;
}
}
Call.propTypes = {
account : PropTypes.object.isRequired,
hangupCall : PropTypes.func.isRequired,
shareScreen : PropTypes.func,
currentCall : PropTypes.object,
escalateToConference : PropTypes.func,
localMedia : PropTypes.object,
targetUri : PropTypes.string,
generatedVideoTrack : PropTypes.bool,
callKeepSendDtmf : PropTypes.func,
callKeepToggleMute : PropTypes.func
};
export default Call;
diff --git a/app/components/ConferenceBox.js b/app/components/ConferenceBox.js
index b950e24..80baa7f 100644
--- a/app/components/ConferenceBox.js
+++ b/app/components/ConferenceBox.js
@@ -1,694 +1,694 @@
'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});
}
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(
);
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,
toggleSpeakerPhone : PropTypes.func,
speakerPhoneEnabled : PropTypes.bool
};
export default ConferenceBox;
diff --git a/app/components/NotificationCenter.js b/app/components/NotificationCenter.js
index 40142d7..52640bc 100644
--- a/app/components/NotificationCenter.js
+++ b/app/components/NotificationCenter.js
@@ -1,171 +1,171 @@
import React, { Component } from 'react';
import { ProgressBar, Colors, Snackbar } from 'react-native-paper';
import moment from 'moment';
import autoBind from 'auto-bind';
import styles from '../assets/styles/blink/_StatusBox.scss';
import config from '../config';
class NotificationCenter extends Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
visible: false,
message: null,
title: null,
autoDismiss: null,
action: null
}
}
componentDidMount() {
console.log('Notification Center mounted');
}
componentWillUnmount() {
console.log('Notification Center will unmount');
}
postSystemNotification(title, options={}) { // eslint-disable-line space-infix-ops
this.setState({
visible: true,
autoDismiss: 10,
title: title,
message: options.body,
action: null
});
}
postConferenceInvite(originator, room, cb) {
// if (originator.uri.endsWith(config.defaultGuestDomain)) {
// return;
// }
const idx = room.indexOf('@');
if (idx === -1) {
return;
}
const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss');
const action = {
label: 'Join',
onPress: () => { cb(room); }
};
this.setState({
visible: true,
message: `${(originator.displayName || originator.uri)} invited you to join conference room ${room.substring(0, idx)} on ${currentDate}`,
title: 'Conference Invite',
autoDismiss: 20,
action: action,
});
}
postMissedCall(originator, cb) {
const currentDate = moment().format('MMMM Do YYYY [at] HH:mm:ss');
let action;
if (originator.uri.endsWith(config.defaultGuestDomain)) {
action = null;
} else {
action = {
label: 'Call',
onPress: () => { cb(originator.uri); }
};
}
this.setState({
visible: true,
message: `From ${(originator.displayName || originator.uri)}
On ${currentDate}`,
title: 'Missed Call',
autoDismiss: 0,
action: action
});
}
postFileUploadProgress(filename, cb) {
this.setState({
visible: true,
message: `${filename}`,
title: 'Uploading file',
autoDismiss: 0,
action: {
label: 'OK',
onPress: () => cb()
},
// children: (
//
//
//
// )
});
}
editFileUploadNotification(progress, notification) {
if (progress === undefined) {
progress = 100;
}
this.setState({
visible: true,
message: `${filename}`,
title: 'Upload Successful',
autoDismiss: 3,
action: null
});
}
removeFileUploadNotification(notification) {
let timer = setTimeout(() => {
this.setState({visible: false});
}, 3000);
}
removeNotification(notification) {
this.setState({visible: false});
}
postFileUploadFailed(filename) {
this.setState({
visible: true,
message: `Uploading of ${filename} failed`,
title: 'File sharing failed',
autoDismiss: 10,
action: null
});
}
postFileShared(file, cb) {
const uploader = file.uploader.displayName || file.uploader.uri || file.uploader;
this.setState({
visible: true,
message: `${uploader} shared ${file.filename}`,
title: 'File shared',
autoDismiss: 10,
action: {
label: 'Show Files',
onPress: () => cb()
}
});
}
render() {
- console.log('showing snackbar');
+ //console.log('showing snackbar');
return (
this.setState({ visible: false, message: null, title: null })}
action={this.state.action}
>
{this.state.title} {this.state.message}
);
}
}
export default NotificationCenter;