diff --git a/app/assets/styles/blink/_AudioCallBox.scss b/app/assets/styles/blink/_AudioCallBox.scss index 1f97f51..63c7fde 100644 --- a/app/assets/styles/blink/_AudioCallBox.scss +++ b/app/assets/styles/blink/_AudioCallBox.scss @@ -1,72 +1,96 @@ .container { flex: 1; } .userIconContainer { padding-top: 20px; margin: 0 auto; } +.tabletUserIconContainer { + padding-top: 60px; + margin: 0 auto; +} + .appbarContainer { background-color: rgba(34,34,34,.7); z-index: 1; } .portraitButtonContainer { justify-self: flex-end; flex-direction: row; margin: 0 auto; margin-top:auto; bottom: 20; + margin-bottom: 50px; +} + +.tabletPortraitButtonContainer { + justify-self: flex-end; + flex-direction: row; + margin: 0 auto; + margin-top:auto; + bottom: 40; margin-bottom: 40px; } .landscapeButtonContainer { justify-self: flex-end; flex-direction: row; margin: 0 auto; margin-top:auto; bottom: 10; margin-bottom: 0px; } +.tabletLandscapeButtonContainer { + justify-self: flex-end; + flex-direction: row; + margin: 0 auto; + margin-top:auto; + bottom: 40; + margin-bottom: 0px; +} + .activity { margin-top: 30px; } .button { background-color: white; margin: 10px; padding-top: 5px; padding-left: 0px; } .iosButton { background-color: rgba(#F9F9F9, .7); margin: 10px; padding-top: 5px; } .androidButton { background-color: rgba(#F9F9F9, .7); margin: 10px; padding-top: 1px; } .hangupButton { background-color: rgba(#a94442, .8); } .displayName { padding-top: 10px; font-size: 30px; text-align: center; color: white; } + .uri { padding: 0px; font-size: 18px; text-align: center; color: white; } diff --git a/app/assets/styles/blink/_LocalMedia.scss b/app/assets/styles/blink/_LocalMedia.scss index 49c5190..1ef7794 100644 --- a/app/assets/styles/blink/_LocalMedia.scss +++ b/app/assets/styles/blink/_LocalMedia.scss @@ -1,22 +1,31 @@ .container { flex: 1; height: 100%; width: 100%; } .video { } +.tabletButtonContainer { + position: absolute; + bottom: 60; + width: 100%; + z-index: 99; + justify-content: center; + align-items: center; +} + .buttonContainer { position: absolute; 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/_VideoBox.scss b/app/assets/styles/blink/_VideoBox.scss index a2ad83b..960abaf 100644 --- a/app/assets/styles/blink/_VideoBox.scss +++ b/app/assets/styles/blink/_VideoBox.scss @@ -1,78 +1,92 @@ .container { flex: 1; } .remoteVideoContainer { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } .reconnectContainer { margin-top: 150px; } .localVideoContainer { justify-content: flex-end; } .video { width: 100%; height: 100%; object-fit: cover; } .localVideo { position: absolute; height: 80px; width: 100px; object-fit: cover; background-color: white; top: 10; left: 10; border-radius: 10px; } .portraitButtonContainer { flex-direction: row; margin: 0 auto; margin-bottom: 40px; } .landscapeButtonContainer { flex-direction: row; margin: 0 auto; margin-bottom: 0px; } +.tabletPortraitButtonContainer { + flex-direction: row; + margin: 0 auto; + margin-bottom: 40px; + bottom: 60; +} + +.tabletLandscapeButtonContainer { + flex-direction: row; + margin: 0 auto; + margin-bottom: 0px; + bottom: 60; +} + .buttonContainer { position: absolute; left: 0; bottom: 0; right: 0; } .button { background-color: rgba(#F9F9F9, .7); margin: 10px; padding-top: 5px; } .iosButton { background-color: rgba(#F9F9F9, .7); margin: 10px; padding-top: 5px; } .androidButton { background-color: rgba(#F9F9F9, .7); margin: 10px; padding-top: 1px; } .hangupButton { background-color: rgba(#a94442, .5); } diff --git a/app/components/AudioCallBox.js b/app/components/AudioCallBox.js index 52ef5d7..b987c6d 100644 --- a/app/components/AudioCallBox.js +++ b/app/components/AudioCallBox.js @@ -1,259 +1,269 @@ 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 EscalateConferenceModal from './EscalateConferenceModal'; import CallOverlay from './CallOverlay'; import DTMFModal from './DTMFModal'; import UserIcon from './UserIcon'; import styles from '../assets/styles/blink/_AudioCallBox.scss'; import utils from '../utils'; class AudioCallBox extends Component { constructor(props) { super(props); autoBind(this); this.state = { active : false, audioMuted : false, showDtmfModal : false, showEscalateConferenceModal : false, call : this.props.call, reconnectingCall : this.props.reconnectingCall }; 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 } 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) { this.props.callKeepToggleMute(false); track.enabled = true; this.setState({audioMuted: false}); } else { track.enabled = false; this.props.callKeepToggleMute(true); this.setState({audioMuted: true}); } } showDtmfModal() { this.setState({showDtmfModal: true}); } hideDtmfModal() { this.setState({showDtmfModal: false}); } toggleEscalateConferenceModal() { this.setState({ showEscalateConferenceModal: !this.state.showEscalateConferenceModal }); } render() { + let buttonContainerClass; + let userIconContainerClass; + let remoteIdentity = {uri: this.props.remoteUri, displayName: this.props.remoteDisplayName}; 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; - const buttonSize = 34; + if (this.props.isTablet) { + buttonContainerClass = this.props.orientation === 'landscape' ? styles.tabletLandscapeButtonContainer : styles.tabletPortraitButtonContainer; + userIconContainerClass = styles.tabletUserIconContainer; + } else { + buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer; + userIconContainerClass = styles.userIconContainer; + } + + const buttonSize = this.props.isTablet ? 40 : 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/Call.js b/app/components/Call.js index 4b9bf66..7467da7 100644 --- a/app/components/Call.js +++ b/app/components/Call.js @@ -1,410 +1,412 @@ 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 uuid from 'react-native-uuid'; import AudioCallBox from './AudioCallBox'; import LocalMedia from './LocalMedia'; import VideoBox from './VideoBox'; import config from '../config'; import utils from '../utils'; class Call extends Component { constructor(props) { super(props); autoBind(this); this.defaultWaitInterval = 60; // until we can connect or reconnect this.waitCounter = 0; this.waitInterval = this.defaultWaitInterval; let audioOnly = false; if (this.props.localMedia && this.props.localMedia.getVideoTracks().length === 0) { audioOnly = true; } let remoteUri = ''; let remoteDisplayName = ''; let callState = null; let direction = 'outgoing'; let callUUID; let callEnded = false; this.mediaIsPlaying = false; if (this.props.call !== null) { // If current call is available on mount we must have incoming this.props.call.on('stateChanged', this.callStateChanged); callState = this.props.call.state; remoteUri = this.props.call.remoteIdentity.uri; direction = this.props.call.direction; callUUID = this.props.call.id; remoteDisplayName = this.props.call.remoteIdentity.displayName; } else { remoteUri = this.props.targetUri; remoteDisplayName = this.props.targetUri; callUUID = this.props.callUUID; } if (this.props.connection) { this.props.connection.on('stateChanged', this.connectionStateChanged); } this.state = { audioOnly: audioOnly, remoteUri: remoteUri, remoteDisplayName: remoteDisplayName, connection: this.props.connection, accountId: this.props.account ? this.props.account.id : null, callState: callState, direction: direction, callUUID: callUUID, reconnectingCall: this.props.reconnectingCall } } mediaPlaying() { if (this.state.direction === 'incoming') { this.answerCall(); } else { this.mediaIsPlaying = true; } } componentDidMount() { if (this.state.direction === 'outgoing') { this.startCallWhenReady(); } } componentWillUnmount() { //console.log('Call: will unmount'); } lookupContact() { let remoteUri = ''; let remoteDisplayName = ''; if (this.props.call !== null) { remoteUri = this.props.call.remoteIdentity.uri; remoteDisplayName = this.props.call.remoteIdentity.displayName || this.props.call.remoteIdentity.uri; } else { remoteUri = this.props.targetUri; remoteDisplayName = this.props.targetUri; } if (remoteUri.indexOf('3333@') > -1) { remoteDisplayName = 'Video Test'; } else if (remoteUri.indexOf('4444@') > -1) { remoteDisplayName = 'Echo Test'; } else if (this.props.contacts) { let username = remoteUri.split('@')[0]; let isPhoneNumber = username.match(/^(\+|0)(\d+)$/); if (isPhoneNumber) { var contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', username); } else { var contact_obj = this.findObjectByKey(this.props.contacts, 'remoteParty', remoteUri); } if (contact_obj) { remoteDisplayName = contact_obj.displayName; this.setState({remoteDisplayName: remoteDisplayName}); if (isPhoneNumber) { remoteUri = username; } } else { if (isPhoneNumber) { remoteUri = username; remoteDisplayName = username; } } } this.setState({remoteDisplayName: remoteDisplayName, remoteUri: remoteUri }); } //getDerivedStateFromProps(nextProps, state) { UNSAFE_componentWillReceiveProps(nextProps) { //console.log('Call: received props'); // Needed for switching to incoming call while in a call if (this.props.call != null && this.props.call != nextProps.currentCall) { if (nextProps.currentCall != null) { nextProps.currentCall.on('stateChanged', this.callStateChanged); } } if (nextProps.reconnectingCall !== this.state.reconnectingCall) { this.setState({reconnectingCall: nextProps.reconnectingCall}); } } callStateChanged(oldState, newState, data) { //console.log('Call: callStateChanged', oldState, '->', newState); let remoteHasNoVideoTracks; let remoteIsRecvOnly; let remoteIsInactive; let remoteStreams; if (newState === 'established') { this.setState({reconnectingCall: false}); const currentCall = this.props.call; if (currentCall) { remoteStreams = currentCall.getRemoteStreams(); if (remoteStreams) { if (remoteStreams.length > 0) { const remotestream = remoteStreams[0]; remoteHasNoVideoTracks = remotestream.getVideoTracks().length === 0; remoteIsRecvOnly = currentCall.remoteMediaDirections.video[0] === 'recvonly'; remoteIsInactive = currentCall.remoteMediaDirections.video[0] === 'inactive'; } } } if (remoteStreams && (remoteHasNoVideoTracks || remoteIsRecvOnly || remoteIsInactive) && !this.state.audioOnly) { //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(); } } 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 && this.props.localMedia.getVideoTracks().length !== 0) { //console.log('Media type changed to video on accepted'); this.setState({audioOnly: false}); } } this.forceUpdate(); } connectionStateChanged(oldState, newState) { utils.timestampedLog('Call: connection state changed:', oldState, '->' , newState); switch (newState) { case 'closed': break; case 'ready': break; case 'disconnected': if (oldState === 'ready' && this.state.direction === 'outgoing') { utils.timestampedLog('Call: reconnecting the call'); this.waitInterval = this.defaultWaitInterval; this.startCallWhenReady(); } break; default: break; } } findObjectByKey(array, key, value) { for (var i = 0; i < array.length; i++) { if (array[i][key] === value) { return array[i]; } } return null; } async startCallWhenReady() { if (!this.props.callUUID || !this.props.targetUri) { return; } utils.timestampedLog('Call: start call', this.props.callUUID, 'when ready to', this.props.targetUri); this.waitCounter = 0; utils.timestampedLog('Call: waiting for establishing call', this.waitInterval, 'seconds'); let diff = 0; while (this.waitCounter < this.waitInterval) { if (this.userHangup) { this.hangupCall('user_cancelled'); return; } if (this.waitCounter >= this.waitInterval - 1) { utils.timestampedLog('Call: terminating conference', this.props.callUUID, 'that did not start yet'); this.hangupCall('timeout'); } if (!this.props.connection || this.props.connection.state !== 'ready' || this.props.registrationState !== 'registered' || !this.mediaIsPlaying ) { utils.timestampedLog('Call: waiting for connection', this.waitInterval - this.waitCounter, 'seconds'); await this._sleep(1000); } else { this.waitCounter = 0; this.call(); return; } this.waitCounter++; } } _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } call() { if (this.props.localMedia === null) { console.log('Call: cannot create new call without local media'); return; } this.lookupContact(); let options = {pcConfig: {iceServers: config.iceServers}, id: this.props.callUUID}; options.localStream = this.props.localMedia; let call = this.props.account.call(this.props.targetUri, options); call.on('stateChanged', this.callStateChanged); } answerCall() { if (this.props.call && this.props.call.state === 'incoming') { this.lookupContact(); let options = {pcConfig: {iceServers: config.iceServers}}; options.localStream = this.props.localMedia; this.props.call.answer(options); } } hangupCall(reason) { let callUUID = this.props.call ? this.props.call._callkeepUUID : this.props.callUUID; this.waitInterval = this.defaultWaitInterval; this.props.callUUID || this.props.call._callkeepUUID; if (this.props.call) { this.props.call.removeListener('stateChanged', this.callStateChanged); } if (this.props.connection) { this.props.connection.removeListener('stateChanged', this.connectionStateChanged); } if (this.waitCounter > 0) { this.waitCounter = this.waitInterval; } this.props.hangupCall(callUUID, reason); } render() { let box = null; if (this.props.localMedia !== null) { if (this.state.audioOnly) { box = ( ); } else { if (this.props.call != null && (this.props.call.state === 'established' || this.props.call.state === 'terminated')) { box = ( ); } else { if (this.props.call && this.props.call.state === 'terminated') { //console.log('Skip render local media'); // do not render } else { //console.log('Render local media'); box = ( ); } } } } return box; } } Call.propTypes = { targetUri : PropTypes.string.isRequired, account : PropTypes.object, hangupCall : PropTypes.func, connection : PropTypes.object, registrationState : PropTypes.string, call : PropTypes.object, localMedia : PropTypes.object, shareScreen : PropTypes.func, escalateToConference : PropTypes.func, generatedVideoTrack : PropTypes.bool, callKeepSendDtmf : PropTypes.func, callKeepToggleMute : PropTypes.func, speakerPhoneEnabled : PropTypes.bool, callUUID : PropTypes.string, contacts : PropTypes.array, intercomDtmfTone : PropTypes.string, orientation : PropTypes.string, isTablet : PropTypes.bool, reconnectingCall : PropTypes.bool }; export default Call; diff --git a/app/components/LocalMedia.js b/app/components/LocalMedia.js index dbce92b..eaa99ae 100644 --- a/app/components/LocalMedia.js +++ b/app/components/LocalMedia.js @@ -1,80 +1,84 @@ 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(); } componentDidMount() { this.props.mediaPlaying(); } hangupCall(event) { event.preventDefault(); this.props.hangupCall('stop_preview'); } render() { let {height, width} = Dimensions.get('window'); let videoStyle = { height, width }; const streamUrl = this.props.localMedia ? this.props.localMedia.toURL() : null; + const buttonSize = this.props.isTablet ? 40 : 34; + const buttonContainerClass = this.props.isTablet ? styles.tabletButtonContainer : styles.buttonContainer; return ( - - + + ); } } LocalMedia.propTypes = { call : PropTypes.object, hangupCall : PropTypes.func, localMedia : PropTypes.object.isRequired, remoteUri : PropTypes.string, remoteDisplayName : PropTypes.string, mediaPlaying : PropTypes.func.isRequired, generatedVideoTrack : PropTypes.bool, connection : PropTypes.object, - accountId : PropTypes.string + accountId : PropTypes.string, + orientation : PropTypes.string, + isTablet : PropTypes.bool }; export default LocalMedia; diff --git a/app/components/VideoBox.js b/app/components/VideoBox.js index 0892732..255d745 100644 --- a/app/components/VideoBox.js +++ b/app/components/VideoBox.js @@ -1,361 +1,372 @@ 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 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, 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() { this.setState({ localStream: this.props.call.getLocalStreams()[0], localVideoShow: true, remoteStream: this.props.call.getRemoteStreams()[0], remoteVideoShow: true }); if (this.props.call) { this.props.call.on('stateChanged', this.callStateChanged); } this.armOverlayTimer(); } componentWillUnmount() { 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}); } handleRemoteResize(event, target) { 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) { return null; } // '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, '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 buttonContainerClass; + let userIconContainerClass; + 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; + + const buttonSize = this.props.isTablet ? 40 : 34; + + if (this.props.isTablet) { + buttonContainerClass = this.props.orientation === 'landscape' ? styles.tabletLandscapeButtonContainer : styles.tabletPortraitButtonContainer; + userIconContainerClass = styles.tabletUserIconContainer; + } else { + buttonContainerClass = this.props.orientation === 'landscape' ? styles.landscapeButtonContainer : styles.portraitButtonContainer; + userIconContainerClass = styles.userIconContainer; + } if (this.state.callOverlayVisible) { let content = ( ); if (this.props.intercomDtmfTone) { content = ( ); } buttons = ({content}); } return ( {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;