diff --git a/app/components/AudioCallBox.js b/app/components/AudioCallBox.js index 66805be..d8066b4 100644 --- a/app/components/AudioCallBox.js +++ b/app/components/AudioCallBox.js @@ -1,282 +1,259 @@ import React, { Component } from 'react'; import { View, Platform } from 'react-native'; import { IconButton, Dialog, Text, ActivityIndicator, Colors } from 'react-native-paper'; - import PropTypes from 'prop-types'; import autoBind from 'auto-bind'; -import Logger from "../../Logger"; +import EscalateConferenceModal from './EscalateConferenceModal'; import CallOverlay from './CallOverlay'; import DTMFModal from './DTMFModal'; -import EscalateConferenceModal from './EscalateConferenceModal'; import UserIcon from './UserIcon'; - -import utils from '../utils'; - import styles from '../assets/styles/blink/_AudioCallBox.scss'; - -const logger = new Logger("AudioCallBox"); +import utils from '../utils'; class AudioCallBox extends Component { constructor(props) { super(props); autoBind(this); - this.userHangup = false; - this.state = { active : false, audioMuted : false, showDtmfModal : false, showEscalateConferenceModal : false, call : this.props.call, reconnectingCall : this.props.reconnectingCall }; - // this.speechEvents = null; this.remoteAudio = React.createRef(); + this.userHangup = false; } 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.state.call != null) { switch (this.state.call.state) { case 'established': this.attachStream(this.state.call); break; case 'incoming': this.props.mediaPlaying(); // fall through default: this.state.call.on('stateChanged', this.callStateChanged); break; } } else { this.props.mediaPlaying(); } } componentWillUnmount() { if (this.state.call != null) { this.state.call.removeListener('stateChanged', this.callStateChanged); } } //getDerivedStateFromProps(nextProps, state) { UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.call && nextProps.call !== this.state.call) { if (nextProps.call.state === 'established') { this.attachStream(nextProps.call); this.setState({reconnectingCall: false}); } nextProps.call.on('stateChanged', this.callStateChanged); if (this.state.call !== null) { this.state.call.removeListener('stateChanged', this.callStateChanged); } this.setState({call: nextProps.call}); } if (nextProps.reconnectingCall != this.state.reconnectingCall) { console.log('Audio box got prop reconnecting', nextProps.reconnectingCall); this.setState({reconnectingCall: nextProps.reconnectingCall}); } } componentWillUnmount() { if (this.state.call != null) { this.state.call.removeListener('stateChanged', this.callStateChanged); } clearTimeout(this.callTimer); } callStateChanged(oldState, newState, data) { if (newState === 'established') { this.attachStream(this.state.call); this.setState({reconnectingCall: false}); } } 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('user_press_hangup'); this.userHangup = true; } cancelCall(event) { event.preventDefault(); this.props.hangupCall('user_cancelled'); } muteAudio(event) { event.preventDefault(); const localStream = this.state.call.getLocalStreams()[0]; const track = localStream.getAudioTracks()[0]; if(this.state.audioMuted) { - //console.log('Unmute microphone'); this.state.callKeepToggleMute(false); track.enabled = true; this.setState({audioMuted: false}); } else { - //console.log('Mute microphone'); track.enabled = false; this.state.callKeepToggleMute(true); this.setState({audioMuted: true}); } } showDtmfModal() { this.setState({showDtmfModal: true}); } hideDtmfModal() { this.setState({showDtmfModal: false}); } toggleEscalateConferenceModal() { this.setState({ showEscalateConferenceModal: !this.state.showEscalateConferenceModal }); } -// {this.props.orientation !== 'landscape' && !this.userHangup && (!this.state.call || (this.state.call && this.state.call.state !== 'established')) ? - render() { let remoteIdentity = {uri: this.props.remoteUri, displayName: this.props.remoteDisplayName}; - - const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton; let displayName = (this.props.remoteDisplayName && this.props.remoteUri !== this.props.remoteDisplayName) ? this.props.remoteDisplayName: this.props.remoteUri; let buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer; - //console.log('Audio box reconnecting', this.state.reconnectingCall); + + const buttonSize = 34; + const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton; return ( {displayName} { (this.props.remoteDisplayName && this.props.remoteUri !== this.props.remoteDisplayName) ? {this.props.remoteUri} : null } {this.props.orientation !== 'landscape' && !this.userHangup && this.state.reconnectingCall ? : null } {this.state.call && this.state.call.state === 'established' ? : } ); } } AudioCallBox.propTypes = { remoteUri : PropTypes.string.isRequired, remoteDisplayName : PropTypes.string, call : PropTypes.object, connection : PropTypes.object, accountId : PropTypes.string, escalateToConference : PropTypes.func, hangupCall : PropTypes.func, mediaPlaying : PropTypes.func, callKeepSendDtmf : PropTypes.func, callKeepToggleMute : PropTypes.func, toggleSpeakerPhone : PropTypes.func, speakerPhoneEnabled : PropTypes.bool, orientation : PropTypes.string, isTablet : PropTypes.bool, reconnectingCall : PropTypes.bool }; export default AudioCallBox; diff --git a/app/components/ReadyBox.js b/app/components/ReadyBox.js index 0d60782..d59d100 100644 --- a/app/components/ReadyBox.js +++ b/app/components/ReadyBox.js @@ -1,265 +1,261 @@ 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, Platform} from 'react-native'; import { IconButton, Title, Button } from 'react-native-paper'; import ConferenceModal from './ConferenceModal'; 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, + targetUri: '', contacts: this.props.contacts, selectedContact: null, showConferenceModal: false, sticky: false, favoriteUris: this.props.favoriteUris, blockedUris: this.props.blockedUris, historyFilter: null }; } getTargetUri() { const defaultDomain = this.props.account.id.substring(this.props.account.id.indexOf('@') + 1); return utils.normalizeUri(this.state.targetUri, defaultDomain); } async componentDidMount() { - //console.log('Ready now'); if (this.state.targetUri) { console.log('We must call', this.state.targetUri); } } filterHistory(filter) { - //console.log('set historyFilter', filter); this.setState({'historyFilter': filter}); this.handleTargetChange(''); } handleTargetChange(value, contact) { let new_value = value; if (contact) { if (this.state.targetUri === contact.uri) { new_value = ''; } } if (this.state.targetUri === value) { new_value = ''; } if (new_value === '') { contact = null; } this.setState({targetUri: new_value, selectedContact: contact}); } handleTargetSelect() { if (this.props.connection === null) { this.props._notificationCenter.postSystemNotification("Server unreachable", {timeout: 2}); return; } // the user pressed enter, start a video call by default if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(this.state.targetUri, {audio: true, video: true}); } 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}); } } handleAudioCall(event) { event.preventDefault(); if (this.state.targetUri.endsWith(`@${config.defaultConferenceDomain}`)) { this.props.startConference(this.state.targetUri, {audio: true, video: false}); } 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, {audio: true, video: false}); } else { this.props.startCall(this.getTargetUri(), {audio: true, video: true}); } } handleConferenceCall(targetUri, options={audio: true, video: true, conference: true}) { if (targetUri) { this.props.startConference(targetUri, options); } this.setState({showConferenceModal: false}); } render() { //utils.timestampedLog('Render ready'); const defaultDomain = `${config.defaultDomain}`; let uriClass = styles.portraitUriInputBox; let uriGroupClass = styles.portraitUriButtonGroup; let titleClass = styles.portraitTitle; const buttonClass = (Platform.OS === 'ios') ? styles.iosButton : styles.androidButton; if (this.props.isTablet) { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTabletTitle : styles.portraitTabletTitle; } else { titleClass = this.props.orientation === 'landscape' ? styles.landscapeTitle : styles.portraitTitle; } if (this.props.isTablet) { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriButtonGroup : styles.portraitTabletUriButtonGroup; } else { uriGroupClass = this.props.orientation === 'landscape' ? styles.landscapeUriButtonGroup : styles.portraitUriButtonGroup; } if (this.props.isTablet) { uriClass = this.props.orientation === 'landscape' ? styles.landscapeTabletUriInputBox : styles.portraitTabletUriInputBox; } else { uriClass = this.props.orientation === 'landscape' ? styles.landscapeUriInputBox : styles.portraitUriInputBox; } const historyClass = this.props.orientation === 'landscape' ? styles.landscapeHistory : styles.portraitHistory; return ( {((this.state.favoriteUris.length > 0 || this.state.blockedUris.length > 0 ) || (this.state.favoriteUris.length === 0 && this.state.historyFilter === 'favorite') || (this.state.blockedUris.length === 0 && this.state.historyFilter === 'blocked') ) ? {(this.state.favoriteUris.length > 0 && this.state.historyFilter !== 'favorite')? : null} {(this.state.blockedUris.length > 0 && this.state.historyFilter !== 'blocked')? : null} : null} {this.props.isTablet && 0? : null} ); } } ReadyBox.propTypes = { account : PropTypes.object.isRequired, password : PropTypes.string.isRequired, config : PropTypes.object.isRequired, startCall : PropTypes.func.isRequired, startConference : PropTypes.func.isRequired, - missedTargetUri : PropTypes.string, contacts : PropTypes.array, orientation : PropTypes.string, isTablet : PropTypes.bool, refreshHistory : PropTypes.bool, cacheHistory : PropTypes.func, serverHistory : PropTypes.array, localHistory : PropTypes.array, myDisplayName : PropTypes.string, myPhoneNumber : PropTypes.string, deleteHistoryEntry: PropTypes.func, setFavoriteUri : PropTypes.func, setBlockedUri : PropTypes.func, favoriteUris : PropTypes.array, blockedUris : PropTypes.array }; export default ReadyBox; diff --git a/app/components/VideoBox.js b/app/components/VideoBox.js index b11621d..0892732 100644 --- a/app/components/VideoBox.js +++ b/app/components/VideoBox.js @@ -1,445 +1,361 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import dtmf from 'react-native-dtmf'; import debug from 'react-native-debug'; import autoBind from 'auto-bind'; import { IconButton, ActivityIndicator, Colors } from 'react-native-paper'; import { View, Dimensions, TouchableWithoutFeedback, Platform } 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.userHangup = false; this.state = { callOverlayVisible: true, audioMuted: false, videoMuted: false, localVideoShow: false, remoteVideoShow: false, remoteSharesScreen: false, showEscalateConferenceModal: false, localStream: null, remoteStream: null, showDtmfModal: false, doorOpened: false }; this.overlayTimer = null; this.localVideo = React.createRef(); this.remoteVideo = React.createRef(); + this.userHangup = false; } callStateChanged(oldState, newState, data) { this.forceUpdate(); } openDoor() { const tone = this.props.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('VideoBox: did mount'); - console.log('Call is', this.props.call); - 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}); + if (this.props.call) { + this.props.call.on('stateChanged', this.callStateChanged); + } this.armOverlayTimer(); } componentWillUnmount() { - // clearTimeout(this.overlayTimer); - // this.remoteVideo.current.removeEventListener('playing', this.handleRemoteVideoPlaying); - // this.exitFullscreen(); if (this.props.call != null) { this.props.call.removeListener('stateChanged', this.callStateChanged); } } 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.props.callKeepToggleMute(false); this.setState({audioMuted: false}); } else { DEBUG('Mute microphone'); track.enabled = false; this.props.callKeepToggleMute(true); 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}); } } } toggleCamera(event) { event.preventDefault(); const localStream = this.state.localStream; if (localStream.getVideoTracks().length > 0) { const track = localStream.getVideoTracks()[0]; track._switchCamera(); } } hangupCall(event) { event.preventDefault(); this.props.hangupCall('user_press_hangup'); this.userHangup = true; } cancelCall(event) { event.preventDefault(); this.props.hangupCall('user_cancelled'); } escalateToConference(participants) { this.props.escalateToConference(participants); } armOverlayTimer() { clearTimeout(this.overlayTimer); this.overlayTimer = setTimeout(() => { this.setState({callOverlayVisible: false}); }, 4000); } toggleCallOverlay() { this.setState({callOverlayVisible: !this.state.callOverlayVisible}); } toggleEscalateConferenceModal() { this.setState({ callOverlayVisible : false, showEscalateConferenceModal : !this.state.showEscalateConferenceModal }); } render() { - if (this.props.call == null) { + if (this.props.call === null) { return null; } - //console.log('Render Video Box in state', this.props.call.state); + // 'mirror' : !this.props.call.sharingScreen && !this.props.generatedVideoTrack, + // we do not want mirrored local video once the call has started, just in preview 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; - let buttons; 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 buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer; + const buttonSize = 34; 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 = ( - // // - - // // - // ); let content = ( ); if (this.props.intercomDtmfTone) { content = ( ); } buttons = ({content}); - } else { - // watermark = ( - // - // - // - // ); } - //console.log('local media stream in videobox', this.state); - return ( - {/* */} - {/* {watermark} */} - {/* */} {this.state.remoteVideoShow && !this.props.reconnectingCall ? : null } { this.state.localVideoShow ? : null } {this.props.reconnectingCall ? : null } {buttons} ); } } VideoBox.propTypes = { call : PropTypes.object, connection : PropTypes.object, accountId : PropTypes.string, remoteUri : PropTypes.string, remoteDisplayName : PropTypes.string, localMedia : PropTypes.object, hangupCall : PropTypes.func, shareScreen : PropTypes.func, escalateToConference : PropTypes.func, generatedVideoTrack : PropTypes.bool, callKeepSendDtmf : PropTypes.func, callKeepToggleMute : PropTypes.func, toggleSpeakerPhone : PropTypes.func, speakerPhoneEnabled : PropTypes.bool, intercomDtmfTone : PropTypes.string, orientation : PropTypes.string, isTablet : PropTypes.bool, reconnectingCall : PropTypes.bool }; export default VideoBox;