diff --git a/app/CallManager.js b/app/CallManager.js index 41180c2..f250bd6 100644 --- a/app/CallManager.js +++ b/app/CallManager.js @@ -1,413 +1,412 @@ import events from 'events'; import Logger from '../Logger'; import uuid from 'react-native-uuid'; import { Platform, PermissionsAndroid } from 'react-native'; import utils from './utils'; const logger = new Logger('CallManager'); import { CONSTANTS as CK_CONSTANTS } from 'react-native-callkeep'; // https://github.com/react-native-webrtc/react-native-callkeep /* const CONSTANTS = { END_CALL_REASONS: { FAILED: 1, REMOTE_ENDED: 2, UNANSWERED: 3, ANSWERED_ELSEWHERE: 4, DECLINED_ELSEWHERE: 5, MISSED: 6 } }; */ const options = { ios: { appName: 'Sylk', maximumCallGroups: 1, maximumCallsPerCallGroup: 2, supportsVideo: true, includesCallsInRecents: true, imageName: "Image-1" }, android: { alertTitle: 'Calling account permission', alertDescription: 'Please allow Sylk inside All calling accounts', cancelButton: 'Deny', okButton: 'Allow', imageName: 'phone_account_icon', additionalPermissions: [PermissionsAndroid.PERMISSIONS.CAMERA, PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, PermissionsAndroid.PERMISSIONS.READ_CONTACTS] } }; export default class CallManager extends events.EventEmitter { constructor(RNCallKeep, acceptFunc, rejectFunc, hangupFunc, timeoutFunc, conferenceCallFunc, startCallFromOutsideFunc) { //logger.debug('constructor()'); super(); this.setMaxListeners(Infinity); this._RNCallKeep = RNCallKeep; this._calls = new Map(); this._conferences = new Map(); this._rejectedCalls = new Map(); this._alertedCalls = new Map(); this._decideWhenWebSocketInviteArrives = new Map(); this._timeouts = new Map(); this.sylkAcceptCall = acceptFunc; this.sylkRejectCall = rejectFunc; this.sylkHangupCall = hangupFunc; this.timeoutCall = timeoutFunc; this.conferenceCall = conferenceCallFunc; this.startCallFromOutside = startCallFromOutsideFunc; this._boundRnAccept = this._rnAccept.bind(this); this._boundRnEnd = this._rnEnd.bind(this); this._boundRnMute = this._rnMute.bind(this); this._boundRnActiveAudioCall = this._rnActiveAudioSession.bind(this); this._boundRnDeactiveAudioCall = this._rnDeactiveAudioSession.bind(this); this._boundRnDTMF = this._rnDTMF.bind(this); this._boundRnProviderReset = this._rnProviderReset.bind(this); this.boundRnStartAction = this._startedCall.bind(this); this.boundRnDisplayIncomingCall = this._displayIncomingCall.bind(this); this._RNCallKeep.addEventListener('answerCall', this._boundRnAccept); this._RNCallKeep.addEventListener('endCall', this._boundRnEnd); this._RNCallKeep.addEventListener('didPerformSetMutedCallAction', this._boundRnMute); this._RNCallKeep.addEventListener('didActivateAudioSession', this._boundRnActiveAudioCall); this._RNCallKeep.addEventListener('didDeactivateAudioSession', this._boundRnDeactiveAudioCall.bind(this)); this._RNCallKeep.addEventListener('didPerformDTMFAction', this._boundRnDTMF); this._RNCallKeep.addEventListener('didResetProvider', this._boundRnProviderReset); this._RNCallKeep.addEventListener('didReceiveStartCallAction', this.boundRnStartAction); this._RNCallKeep.addEventListener('didDisplayIncomingCall', this.boundRnDisplayIncomingCall); this._RNCallKeep.setup(options); this._RNCallKeep.addEventListener('checkReachability', () => { this._RNCallKeep.setReachable(); }); } get callKeep() { return this._RNCallKeep; } get count() { return this._calls.size; } get waitingCount() { return this._timeouts.size; } get callUUIDS() { return Array.from( this._calls.keys() ); } get calls() { return [...this._calls.values()]; } get activeCall() { for (let call of this.calls) { if (call.active) { return call; } } } setAvailable(available) { this.callKeep.setAvailable(available); } backToForeground() { utils.timestampedLog('Callkeep: bring app to the foreground'); this.callKeep.backToForeground(); } setMutedCall(callUUID, mute) { utils.timestampedLog('Callkeep: set muted: ', mute); this.callKeep.setMutedCall(callUUID, mute); } startCall(callUUID, targetUri, targetName, hasVideo) { utils.timestampedLog('Callkeep: start outgoing call', callUUID); if (Platform.OS === 'ios') { this.callKeep.startCall(callUUID, targetUri, targetUri, 'email', hasVideo); } else if (Platform.OS === 'android') { this.callKeep.startCall(callUUID, targetUri, targetUri); } } updateDisplay(callUUID, displayName, uri) { utils.timestampedLog('Callkeep: update display', displayName, uri); this.callKeep.updateDisplay(callUUID, displayName, uri); } sendDTMF(callUUID, digits) { utils.timestampedLog('Callkeep: send DTMF: ', digits); this.callKeep.sendDTMF(callUUID, digits); } setCurrentCallActive(callUUID) { utils.timestampedLog('Callkeep: set call active', callUUID); this.callKeep.setCurrentCallActive(callUUID); } rejectCall(callUUID) { utils.timestampedLog('Callkeep: reject call', callUUID); this.callKeep.rejectCall(callUUID); this._rejectedCalls.set(callUUID, true); } endCalls() { utils.timestampedLog('Callkeep: end all calls'); this.callKeep.endAllCalls(); } endCall(callUUID, reason) { utils.timestampedLog('Callkeep: end call', callUUID, ' with reason', reason); if (reason) { this.callKeep.reportEndCallWithUUID(callUUID, reason); } else { this.callKeep.endCall(callUUID); } this._calls.delete(callUUID); } _rnActiveAudioSession() { utils.timestampedLog('Callkeep: activated audio call'); } _rnDeactiveAudioSession() { utils.timestampedLog('Callkeep: deactivated audio call'); } _rnAccept(data) { let callUUID = data.callUUID.toLowerCase(); utils.timestampedLog('Callkeep: accept callback', callUUID); if (this._conferences.has(callUUID)) { let room = this._conferences.get(callUUID); utils.timestampedLog('Callkeep: accept incoming conference', callUUID); this.callKeep.endCall(callUUID); this._conferences.delete(callUUID); if (this._timeouts.has(callUUID)) { clearTimeout(this._timeouts.get(callUUID)); this._timeouts.delete(callUUID); } utils.timestampedLog('Will start conference to', room); this.conferenceCall(room); // start an outgoing conference call } else if (this._calls.has(callUUID)) { this.sylkAcceptCall(); } else { // We accepted the call before it arrived on web socket // from iOS push notifications this.backToForeground(); this._decideWhenWebSocketInviteArrives.set(callUUID, 'accept'); } } _rnEnd(data) { //get the uuid, find the call with that uuid and ccept it let callUUID = data.callUUID.toLowerCase(); utils.timestampedLog('Callkeep: end callback', callUUID); if (this._conferences.has(callUUID)) { utils.timestampedLog('Callkeep: reject conference invite', callUUID); let room = this._conferences.get(callUUID); this.callKeep.endCall(callUUID); this._conferences.delete(callUUID); if (this._timeouts.has(callUUID)) { clearTimeout(this._timeouts.get(callUUID)); this._timeouts.delete(callUUID); } } else if (this._calls.has(callUUID)) { utils.timestampedLog('Callkeep: hangup call', callUUID); let call = this._calls.get(callUUID); if (call.state === 'incoming') { this.sylkRejectCall(callUUID); } else { this.sylkHangupCall(callUUID); } } else { // We rejected the call before it arrived on web socket // from iOS push notifications utils.timestampedLog('Callkeep: add call', callUUID, 'to the waitings list'); this._decideWhenWebSocketInviteArrives.set(callUUID, 'reject'); this._rejectedCalls.set(callUUID, true); } } _rnMute(data) { utils.timestampedLog('Callkeep: mute ' + data.muted + ' for call', data.callUUID); //get the uuid, find the call with that uuid and mute/unmute it if (this._calls.has(data.callUUID.toLowerCase())) { let call = this._calls.get(data.callUUID.toLowerCase()); const localStream = call.getLocalStreams()[0]; localStream.getAudioTracks()[0].enabled = !data.muted; } } _rnDTMF(data) { utils.timestampedLog('Callkeep: got dtmf for call', data.callUUID); if (this._calls.has(data.callUUID.toLowerCase())) { let call = this._calls.get(data.callUUID.toLowerCase()); utils.timestampedLog('sending webrtc dtmf', data.digits) call.sendDtmf(data.digits); } } _rnProviderReset() { utils.timestampedLog('Callkeep: got a provider reset, clearing down all calls'); this._calls.forEach((call) => { call.terminate(); }) } handleIncomingPushCall(callUUID, notificationContent) { // call is received by push notification if (this._calls.has(callUUID)) { utils.timestampedLog('Callkeep: call', callUUID, 'already handled'); return; } if (this._rejectedCalls.has(callUUID)) { this.callKeep.reportEndCallWithUUID(callUUID, CK_CONSTANTS.END_CALL_REASONS.UNANSWERED); return; } utils.timestampedLog('Callkeep: handle later incoming call', callUUID); let reason = this._decideWhenWebSocketInviteArrives.has(callUUID) ? CK_CONSTANTS.END_CALL_REASONS.FAILED : CK_CONSTANTS.END_CALL_REASONS.UNANSWERED; // if user does not decide anything this will be handled later this._timeouts.set(callUUID, setTimeout(() => { utils.timestampedLog('Callkeep: end call later', callUUID); this.callKeep.reportEndCallWithUUID(callUUID, reason); this._timeouts.delete(callUUID); }, 45000)); } handleIncomingWebSocketCall(call) { call._callkeepUUID = call.id; this._calls.set(call._callkeepUUID, call); utils.timestampedLog('Callkeep: start incoming call', call._callkeepUUID); if (this._timeouts.has(call.id)) { clearTimeout(this._timeouts.get(call.id)); this._timeouts.delete(call.id); } // if the call came via push and was already accepted or rejected if (this._decideWhenWebSocketInviteArrives.get(call._callkeepUUID)) { let action = this._decideWhenWebSocketInviteArrives.get(call._callkeepUUID); utils.timestampedLog('Callkeep: execute action', action); if (action === 'accept') { this.sylkAcceptCall(call.id); } else { this.sylkRejectCall(call.id); } this._decideWhenWebSocketInviteArrives.delete(call._callkeepUUID); } else if (this._rejectedCalls.has(call._callkeepUUID)) { this.sylkRejectCall(call.id); } else { this.showAlertPanel(call); } // Emit event. this._emitSessionsChange(true); } handleOutgoingCall(call, callUUID) { // this is an outgoing call call._callkeepUUID = callUUID; this._calls.set(call._callkeepUUID, call); utils.timestampedLog('Callkeep: start outgoing call', call._callkeepUUID); // Emit event. this._emitSessionsChange(true); } handleConference(callUUID, room, from_uri) { if (this._conferences.has(callUUID)) { return; } utils.timestampedLog('CallKeep: handle conference', callUUID, 'from', from_uri, 'to room', room); this._conferences.set(callUUID, room); this.showConferenceAlertPanel(callUUID, from_uri); // there is no cancel, so we add a timer this._timeouts.set(callUUID, setTimeout(() => { utils.timestampedLog('Callkeep: conference timeout', callUUID); this.timeoutCall(callUUID, from_uri); this.callKeep.reportEndCallWithUUID(callUUID, CK_CONSTANTS.END_CALL_REASONS.MISSED); this._timeouts.delete(callUUID); }, 45000)); this._emitSessionsChange(true); } showConferenceAlertPanel(callUUID, uri) { - //uri = uri.toString(); utils.timestampedLog('Callkeep: show alert panel for conference', callUUID, 'from', uri); if (Platform.OS === 'ios') { this.callKeep.displayIncomingCall(callUUID, uri, uri, 'email', true); } else if (Platform.OS === 'android') { this.callKeep.displayIncomingCall(callUUID, uri, uri); } } _startedCall(data) { utils.timestampedLog("Callkeep: started call from outside"); console.log(data); this.startCallFromOutside(data); } _displayIncomingCall(data) { utils.timestampedLog('Incoming alert panel displayed'); this._alertedCalls.set(data.callUUID.toLowerCase(), true); } showAlertPanel(call) { if (this._alertedCalls.has(call._callkeepUUID)) { utils.timestampedLog('Callkeep: alert panel was already shown for call', call._callkeepUUID); return; } utils.timestampedLog('Callkeep: show alert panel for call', call._callkeepUUID); if (Platform.OS === 'ios') { this.callKeep.displayIncomingCall(call._callkeepUUID, call.remoteIdentity.uri, call.remoteIdentity.displayName, 'email', call.mediaTypes.video); } else if (Platform.OS === 'android') { this.callKeep.displayIncomingCall(call._callkeepUUID, call.remoteIdentity.uri, call.remoteIdentity.displayName); } } _emitSessionsChange(countChanged) { this.emit('sessionschange', countChanged); } destroy() { this._RNCallKeep.removeEventListener('acceptCall', this._boundRnAccept); this._RNCallKeep.removeEventListener('endCall', this._boundRnEnd); this._RNCallKeep.removeEventListener('didPerformSetMutedCallAction', this._boundRnMute); this._RNCallKeep.removeEventListener('didActivateAudioSession', this._boundRnActiveAudioCall); this._RNCallKeep.removeEventListener('didDeactivateAudioSession', this._boundRnDeactiveAudioCall); this._RNCallKeep.removeEventListener('didPerformDTMFAction', this._boundRnDTMF); this._RNCallKeep.removeEventListener('didResetProvider', this._boundRnProviderReset); this._RNCallKeep.removeEventListener('didReceiveStartCallAction', this.boundRnStartAction); this._RNCallKeep.removeEventListener('didDisplayIncomingCall', this.boundRnDisplayIncomingCall); } } diff --git a/app/components/ConferenceBox.js b/app/components/ConferenceBox.js index 81a3ef5..ee8fce5 100644 --- a/app/components/ConferenceBox.js +++ b/app/components/ConferenceBox.js @@ -1,732 +1,732 @@ 'use strict'; import React, {Component, Fragment} from 'react'; import { View, Platform } 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(), + participants: [], showInviteModal: false, showDrawer: false, showFiles: false, shareOverlayVisible: false, - activeSpeakers: props.call.activeParticipants.slice(), + activeSpeakers: [], selfDisplayedLarge: false, eventLog: [], - sharedFiles: props.call.sharedFiles.slice(), + sharedFiles: [], largeVideoStream: null }; const friendlyName = this.props.remoteUri.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(); + const log = this.state.eventLog; 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}`); if (p.identity._uri.search('guest.') === -1) { // used for history item this.props.saveParticipant(this.props.call.id, this.props.remoteUri.split('@')[0], p.identity._uri); } // 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 participants = this.state.participants; 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(); + let stateFiles = this.state.sharedFiles; 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(); + let newActiveSpeakers = this.state.activeSpeakers; 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.remoteUri}/${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.remoteUri}/${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}); } } } toggleCamera(event) { event.preventDefault(); const localStream = this.props.call.getLocalStreams()[0]; if (localStream.getVideoTracks().length > 0) { const track = localStream.getVideoTracks()[0]; track._switchCamera(); } } 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); } inviteParticipants(uris) { console.log('Invite participants', uris); this.props.saveInvitedParties(this.props.call.id, this.props.remoteUri.split('@')[0], uris); this.props.call.inviteParticipants(uris); } 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 remoteUri = this.props.remoteUri.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 buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton; const bottomButtons = []; bottomButtons.push( ); if (this.haveVideo) { bottomButtons.push( ); } bottomButtons.push( ); if (this.haveVideo) { 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, saveParticipant : PropTypes.func, saveInvitedParties : PropTypes.func, previousParticipants: PropTypes.array, remoteUri : PropTypes.string, generatedVideoTrack : PropTypes.bool, toggleSpeakerPhone : PropTypes.func, speakerPhoneEnabled : PropTypes.bool }; export default ConferenceBox;