summaryrefslogtreecommitdiffstats
path: root/webapp/components/webrtc/webrtc_controller.jsx
diff options
context:
space:
mode:
authorenahum <nahumhbl@gmail.com>2016-09-16 15:35:13 -0300
committerGitHub <noreply@github.com>2016-09-16 15:35:13 -0300
commit781ff323db4c70e4ca476f9ef13a04e5aa063585 (patch)
treea9dae870d4e750ad87ee0624d8ea859995b0dbf7 /webapp/components/webrtc/webrtc_controller.jsx
parentdf2d61d94175369bff5a16242f35cb6d7b62d7fb (diff)
downloadchat-781ff323db4c70e4ca476f9ef13a04e5aa063585.tar.gz
chat-781ff323db4c70e4ca476f9ef13a04e5aa063585.tar.bz2
chat-781ff323db4c70e4ca476f9ef13a04e5aa063585.zip
Webrtc client side (#4026)
* WebRTC Server side * WebRTC client side * Bug fixes and improvements * Pushing UI improvements for webrtc (#3728) * Pushing UI improvements for webrtc * Updating webrtc css * PLT-3943 WebRTC P1: bug fixes and improvements * Video resolution set to std, reduce volume of ringtone and flip video horizontally * Fix calling a user B while WebRTC RHS is still opened * Leave RHS opened when call ends, Fix isBusy on popover and channel_header * Fix pre-release feature, RHS & System Console * PLT-3945 - Updating UI for webrtc (#3908) * PLT-3943 Webrtc p1 * Add ongoing call indicator when RHS is opened * UI updates to to webrtc notifcation (#3959)
Diffstat (limited to 'webapp/components/webrtc/webrtc_controller.jsx')
-rw-r--r--webapp/components/webrtc/webrtc_controller.jsx1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/webapp/components/webrtc/webrtc_controller.jsx b/webapp/components/webrtc/webrtc_controller.jsx
new file mode 100644
index 000000000..f9cf241d5
--- /dev/null
+++ b/webapp/components/webrtc/webrtc_controller.jsx
@@ -0,0 +1,1214 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import UserStore from 'stores/user_store.jsx';
+import ChannelStore from 'stores/channel_store.jsx';
+import WebrtcStore from 'stores/webrtc_store.jsx';
+
+import Client from 'client/web_client.jsx';
+import WebSocketClient from 'client/web_websocket_client.jsx';
+import WebrtcSession from 'client/webrtc_session.jsx';
+
+import SearchBox from '../search_bar.jsx';
+import WebrtcHeader from './components/webrtc_header.jsx';
+import ConnectingScreen from 'components/loading_screen.jsx';
+
+import * as WebrtcActions from 'actions/webrtc_actions.jsx';
+
+import * as Utils from 'utils/utils.jsx';
+import {Constants, UserStatuses, WebrtcActionTypes} from 'utils/constants.jsx';
+
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
+
+import ring from 'images/ring.mp3';
+
+const VIDEO_WIDTH = 640;
+const VIDEO_HEIGHT = 360;
+const MIN_ASPECT = 1.777;
+const MAX_ASPECT = 1.778;
+const ALREADY_REGISTERED_ERROR = 477;
+const USERNAME_TAKEN = 476;
+
+export default class WebrtcController extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.mounted = false;
+ this.localMedia = null;
+ this.session = null;
+ this.videocall = null;
+
+ this.handleResize = this.handleResize.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.close = this.close.bind(this);
+ this.clearError = this.clearError.bind(this);
+
+ this.previewVideo = this.previewVideo.bind(this);
+ this.stopRinging = this.stopRinging.bind(this);
+
+ this.handleMakeOffer = this.handleMakeOffer.bind(this);
+ this.handleCancelOffer = this.handleCancelOffer.bind(this);
+ this.handleWebrtcEvent = this.handleWebrtcEvent.bind(this);
+ this.handleVideoCallEvent = this.handleVideoCallEvent.bind(this);
+ this.handleRemoteStream = this.handleRemoteStream.bind(this);
+
+ this.onStatusChange = this.onStatusChange.bind(this);
+ this.onCallDeclined = this.onCallDeclined.bind(this);
+ this.onUnsupported = this.onUnsupported.bind(this);
+ this.onNoAnswer = this.onNoAnswer.bind(this);
+ this.onBusy = this.onBusy.bind(this);
+ this.onDisabled = this.onDisabled.bind(this);
+ this.onFailed = this.onFailed.bind(this);
+ this.onCancelled = this.onCancelled.bind(this);
+ this.onConnectCall = this.onConnectCall.bind(this);
+
+ this.onSessionCreated = this.onSessionCreated.bind(this);
+ this.onSessionError = this.onSessionError.bind(this);
+
+ this.doCall = this.doCall.bind(this);
+ this.doAnswer = this.doAnswer.bind(this);
+ this.doHangup = this.doHangup.bind(this);
+ this.doCleanup = this.doCleanup.bind(this);
+
+ this.renderButtons = this.renderButtons.bind(this);
+ this.onToggleVideo = this.onToggleVideo.bind(this);
+ this.onToggleAudio = this.onToggleAudio.bind(this);
+ this.onToggleRemoteMute = this.onToggleRemoteMute.bind(this);
+ this.toggleIcons = this.toggleIcons.bind(this);
+
+ const currentUser = UserStore.getCurrentUser();
+ const remoteUser = UserStore.getProfile(props.userId);
+ const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.update_at;
+
+ this.state = {
+ windowWidth: Utils.windowWidth(),
+ windowHeight: Utils.windowHeight(),
+ channelId: ChannelStore.getCurrentId(),
+ currentUser,
+ currentUserImage: Client.getUsersRoute() + '/' + currentUser.id + '/image?time=' + currentUser.update_at,
+ remoteUserImage,
+ localMediaLoaded: false,
+ isPaused: false,
+ isMuted: false,
+ isRemotePaused: false,
+ isRemoteMuted: false,
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error: null,
+ ended: null
+ };
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.handleResize);
+ WebrtcStore.addChangedListener(this.handleWebrtcEvent);
+ UserStore.addStatusesChangeListener(this.onStatusChange);
+
+ this.mounted = true;
+ this.previewVideo();
+
+ if (this.props.isCaller) {
+ this.handleMakeOffer();
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.handleResize);
+ WebrtcStore.removeChangedListener(this.handleWebrtcEvent);
+ UserStore.removeStatusesChangeListener(this.onStatusChange);
+ this.mounted = false;
+ this.close();
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if ((nextProps.currentUser !== this.props.currentUser) ||
+ (nextProps.userId !== this.props.userId) ||
+ (nextProps.isCaller !== this.props.isCaller)) {
+ const remoteUser = UserStore.getProfile(nextProps.userId);
+ const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.update_at;
+ this.setState({
+ error: null,
+ remoteUserImage
+ });
+ }
+
+ if (nextProps.isCaller && nextProps.expanded === this.props.expanded) {
+ this.startCall = true;
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.props.isCaller && this.startCall) {
+ this.startCall = false;
+ this.handleMakeOffer();
+ }
+ }
+
+ handleResize() {
+ this.setState({
+ windowWidth: Utils.windowWidth(),
+ windowHeight: Utils.windowHeight()
+ });
+ }
+
+ clearError() {
+ setTimeout(() => {
+ this.setState({error: null, ended: null});
+ }, Constants.WEBRTC_CLEAR_ERROR_DELAY);
+ }
+
+ previewVideo() {
+ if (this.mounted) {
+ if (this.localMedia) {
+ this.setState({
+ localMediaLoaded: true,
+ error: null
+ });
+ this.localMedia.enabled = true;
+ } else {
+ WebrtcSession.getLocalMedia(
+ {
+ audio: true,
+ video: {
+ mandatory: {
+ minAspectRatio: MIN_ASPECT,
+ maxAspectRatio: MAX_ASPECT
+ },
+ width: VIDEO_WIDTH,
+ height: VIDEO_HEIGHT
+ }
+ },
+ this.refs['local-video'],
+ (error, stream) => {
+ if (error) {
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='webrtc.mediaError'
+ defaultMessage='Unable to access Camera and Microphone'
+ />
+ )
+ });
+ return;
+ }
+ this.localMedia = stream;
+ this.setState({
+ localMediaLoaded: true
+ });
+ });
+ }
+ }
+ }
+
+ stopRinging() {
+ if (this.refs.ring) {
+ this.refs.ring.pause();
+ this.refs.ring.currentTime = 0;
+ }
+ }
+
+ handleMakeOffer() {
+ if (UserStore.getStatus(this.props.userId) === UserStatuses.OFFLINE) {
+ this.onStatusChange();
+ } else {
+ const connectingMsg = (
+ <FormattedMessage
+ id='calling_screen'
+ defaultMessage='Calling'
+ />
+ );
+
+ this.setState({
+ isCalling: true,
+ isAnswering: false,
+ callInProgress: false,
+ error: null,
+ ended: null,
+ connectingMsg
+ });
+
+ WebrtcStore.setVideoCallWith(this.props.userId);
+
+ const user = this.state.currentUser;
+ WebSocketClient.sendMessage('webrtc', {
+ action: WebrtcActionTypes.NOTIFY,
+ from_user_id: user.id,
+ to_user_id: this.props.userId
+ });
+ }
+ }
+
+ handleCancelOffer() {
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error: null,
+ ended: null
+ });
+
+ const user = this.state.currentUser;
+ WebSocketClient.sendMessage('webrtc', {
+ action: WebrtcActionTypes.CANCEL,
+ from_user_id: user.id,
+ to_user_id: this.props.userId
+ });
+
+ this.doCleanup();
+ }
+
+ handleWebrtcEvent(message) {
+ switch (message.action) {
+ case WebrtcActionTypes.DECLINE:
+ this.onCallDeclined();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.UNSUPPORTED:
+ this.onUnsupported();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.BUSY:
+ this.onBusy();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.NO_ANSWER:
+ this.onNoAnswer();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.FAILED:
+ this.onFailed();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.ANSWER:
+ this.onConnectCall();
+ break;
+ case WebrtcActionTypes.CANCEL:
+ this.onCancelled();
+ this.clearError();
+ break;
+ case WebrtcActionTypes.MUTED:
+ this.onToggleRemoteMute(message);
+ break;
+ case WebrtcActionTypes.IN_PROGRESS:
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='webrtc.inProgress'
+ defaultMessage='You have a call in progress. Please hangup first.'
+ />
+ )
+ });
+ this.clearError();
+ break;
+ case WebrtcActionTypes.DISABLED:
+ this.onDisabled();
+ this.clearError();
+ break;
+ }
+ }
+
+ handleVideoCallEvent(msg, jsep) {
+ const result = msg.result;
+
+ if (result) {
+ const event = result.event;
+ switch (event) {
+ case 'registered':
+ if (this.state.isCalling) {
+ this.doCall();
+ }
+ break;
+ case 'incomingcall':
+ this.doAnswer(jsep);
+ break;
+ case 'accepted':
+ this.stopRinging();
+
+ if (jsep) {
+ this.videocall.handleRemoteJsep({jsep});
+ }
+ break;
+ case 'hangup':
+ this.doHangup(false);
+ break;
+ }
+ } else {
+ const errorCode = msg.error_code;
+ if (errorCode !== ALREADY_REGISTERED_ERROR && errorCode !== USERNAME_TAKEN) {
+ this.doHangup(true);
+ } else if (this.state.isCalling) {
+ this.doCall();
+ }
+ }
+ }
+
+ handleRemoteStream(stream) {
+ // attaching stream to where they belong
+ this.refs['main-video'].srcObject = stream;
+
+ let isRemotePaused = false;
+ let isRemoteMuted = false;
+ const videoTracks = stream.getVideoTracks();
+ const audioTracks = stream.getAudioTracks();
+ if (!videoTracks || videoTracks.length === 0 || videoTracks[0].muted || !videoTracks[0].enabled) {
+ isRemotePaused = true;
+ }
+
+ if (!audioTracks || audioTracks.length === 0 || audioTracks[0].muted || !audioTracks[0].enabled) {
+ isRemoteMuted = true;
+ }
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: true,
+ isMuted: false,
+ isPaused: false,
+ error: null,
+ ended: null,
+ isRemotePaused,
+ isRemoteMuted
+ });
+ this.toggleIcons();
+ }
+
+ handleClose(e) {
+ e.preventDefault();
+ if (this.state.callInProgress) {
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='webrtc.inProgress'
+ defaultMessage='You have a call in progress. Please hangup first.'
+ />
+ )
+ });
+ } else if (this.state.isCalling) {
+ this.handleCancelOffer();
+ this.close();
+ } else {
+ this.close();
+ }
+ }
+
+ close() {
+ this.doCleanup();
+
+ if (this.session) {
+ this.session.destroy();
+ this.session.disconnect();
+ this.session = null;
+ }
+
+ if (this.localMedia) {
+ WebrtcSession.stopMediaStream(this.localMedia);
+ this.localMedia = null;
+ }
+
+ WebrtcActions.initWebrtc(null, false);
+ }
+
+ onStatusChange() {
+ const status = UserStore.getStatus(this.props.userId);
+
+ if (status === UserStatuses.OFFLINE) {
+ const error = (
+ <FormattedMessage
+ id='webrtc.offline'
+ defaultMessage='{username} is offline'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ );
+
+ if (this.state.isCalling || this.state.isAnswering) {
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error
+ });
+ } else {
+ this.setState({
+ error
+ });
+ }
+ } else if (status !== UserStatuses.OFFLINE && this.state.error) {
+ this.setState({
+ error: null,
+ ended: null
+ });
+ }
+ }
+
+ onCallDeclined() {
+ let error = null;
+
+ if (this.state.isCalling) {
+ error = (
+ <FormattedMessage
+ id='webrtc.declined'
+ defaultMessage='Your call has been declined by {username}'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ );
+ }
+
+ this.stopRinging();
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error
+ });
+
+ this.doCleanup();
+ }
+
+ onUnsupported() {
+ if (this.mounted) {
+ this.stopRinging();
+
+ this.setState({
+ error: (
+ <FormattedMessage
+ id='webrtc.unsupported'
+ defaultMessage='Call to {username} not successful. Their client does not support video calls.'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ ),
+ callInProgress: false,
+ isCalling: false,
+ isAnswering: false
+ });
+ }
+
+ this.doCleanup();
+ }
+
+ onNoAnswer() {
+ let error = null;
+
+ if (this.state.isCalling) {
+ error = (
+ <FormattedMessage
+ id='webrtc.noAnswer'
+ defaultMessage='{username} is not answering the call'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ );
+ }
+ this.stopRinging();
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error
+ });
+
+ this.doCleanup();
+ }
+
+ onBusy() {
+ let error = null;
+
+ if (this.state.isCalling) {
+ error = (
+ <FormattedMessage
+ id='webrtc.busy'
+ defaultMessage='{username} is busy'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ );
+ }
+ this.stopRinging();
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error
+ });
+
+ this.doCleanup();
+ }
+
+ onDisabled() {
+ let error = null;
+
+ if (this.state.isCalling) {
+ error = (
+ <FormattedMessage
+ id='webrtc.disabled'
+ defaultMessage='{username} has WebRTC disabled, and cannot receive calls. To enable the feature, they must go to Account Settings > Advanced > Preview pre-release features and turn on WebRTC.'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ );
+ }
+
+ this.stopRinging();
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error
+ });
+
+ this.doCleanup();
+ }
+
+ onFailed() {
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ isPaused: false,
+ isMuted: false,
+ isRemotePaused: false,
+ isRemoteMuted: false,
+ error: (
+ <FormattedMessage
+ id='webrtc.failed'
+ defaultMessage='There was a problem connecting the video call'
+ />
+ )
+ });
+
+ this.stopRinging();
+
+ this.doCleanup();
+ }
+
+ onCancelled() {
+ if (this.mounted && this.state.isAnswering) {
+ this.stopRinging();
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ error: (
+ <FormattedMessage
+ id='webrtc.cancelled'
+ defaultMessage='{username} cancelled the call'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ )
+ });
+ }
+
+ this.doCleanup();
+ }
+
+ onConnectCall() {
+ Client.webrtcToken(
+ (info) => {
+ const connectingMsg = (
+ <FormattedMessage
+ id='connecting_screen'
+ defaultMessage='Connecting'
+ />
+ );
+
+ this.setState({isAnswering: !this.state.isCalling, connectingMsg});
+ if (this.session) {
+ this.onSessionCreated();
+ } else {
+ const iceServers = [];
+
+ if (info.stun_uri) {
+ iceServers.push({
+ urls: [info.stun_uri]
+ });
+ }
+
+ if (info.turn_uri) {
+ iceServers.push({
+ urls: [info.turn_uri],
+ username: info.turn_username,
+ credential: info.turn_password
+ });
+ }
+
+ this.session = new WebrtcSession({
+ debug: global.mm_config.EnableDeveloper === 'true',
+ server: info.gateway_url,
+ iceServers,
+ token: info.token,
+ success: this.onSessionCreated,
+ error: this.onSessionError
+ });
+ }
+ },
+ () => {
+ this.onSessionError();
+ });
+ }
+
+ onSessionCreated() {
+ if (this.videocall) {
+ this.doCall();
+ } else {
+ this.session.attach({
+ plugin: 'janus.plugin.videocall',
+ success: (plugin) => {
+ this.videocall = plugin;
+ this.videocall.send({message: {request: 'register', username: this.state.currentUser.id}});
+ },
+ error: this.onSessionError,
+ onmessage: this.handleVideoCallEvent,
+ onremotestream: this.handleRemoteStream
+ });
+ }
+ }
+
+ onSessionError() {
+ const user = this.state.currentUser;
+ WebSocketClient.sendMessage('webrtc', {
+ action: WebrtcActionTypes.FAILED,
+ from_user_id: user.id,
+ to_user_id: this.props.userId
+ });
+
+ this.onFailed();
+ }
+
+ doCall() {
+ // delay call so receiver has time to register
+ setTimeout(() => {
+ this.videocall.createOffer({
+ stream: this.localMedia,
+ success: (jsep) => {
+ const body = {request: 'call', username: this.props.userId};
+ this.videocall.send({message: body, jsep});
+ },
+ error: () => {
+ this.doHangup(true);
+ }
+ });
+ }, Constants.WEBRTC_TIME_DELAY);
+ }
+
+ doAnswer(jsep) {
+ this.videocall.createAnswer({
+ jsep,
+ stream: this.localMedia,
+ success: (jsepSuccess) => {
+ const body = {request: 'accept'};
+ this.videocall.send({message: body, jsep: jsepSuccess});
+ },
+ error: () => {
+ this.doHangup(true);
+ }
+ });
+ }
+
+ doHangup(error, manual) {
+ if (this.videocall && this.state.callInProgress) {
+ this.videocall.send({message: {request: 'hangup'}});
+ this.videocall.hangup();
+ this.toggleIcons();
+
+ this.localMedia.getVideoTracks()[0].enabled = true;
+ this.localMedia.getAudioTracks()[0].enabled = true;
+ }
+
+ if (error) {
+ this.onSessionError();
+ return this.doCleanup();
+ }
+ WebrtcStore.setVideoCallWith(null);
+
+ if (manual) {
+ return this.close();
+ }
+
+ this.setState({
+ isCalling: false,
+ isAnswering: false,
+ callInProgress: false,
+ isPaused: false,
+ isMuted: false,
+ isRemotePaused: false,
+ isRemoteMuted: false,
+ error: null,
+ ended: (
+ <FormattedMessage
+ id='webrtc.callEnded'
+ defaultMessage='Call with {username} ended.'
+ values={{
+ username: Utils.displayUsername(this.props.userId)
+ }}
+ />
+ )
+ });
+ this.clearError();
+ return this.doCleanup();
+ }
+
+ doCleanup() {
+ WebrtcStore.setVideoCallWith(null);
+
+ if (this.videocall) {
+ this.videocall.detach();
+ this.videocall = null;
+ }
+ }
+
+ onToggleVideo() {
+ const shouldPause = !this.state.isPaused;
+ if (shouldPause) {
+ this.videocall.unmuteVideo();
+ } else {
+ this.videocall.muteVideo();
+ }
+
+ const user = this.state.currentUser;
+ WebSocketClient.sendMessage('webrtc', {
+ action: WebrtcActionTypes.MUTED,
+ from_user_id: user.id,
+ to_user_id: this.props.userId,
+ type: 'video',
+ mute: shouldPause
+ });
+
+ this.setState({
+ isPaused: shouldPause,
+ error: null,
+ ended: null
+ });
+ }
+
+ onToggleAudio() {
+ const shouldMute = !this.state.isMuted;
+ if (shouldMute) {
+ this.videocall.unmuteAudio();
+ } else {
+ this.videocall.muteAudio();
+ }
+
+ const user = this.state.currentUser;
+ WebSocketClient.sendMessage('webrtc', {
+ action: WebrtcActionTypes.MUTED,
+ from_user_id: user.id,
+ to_user_id: this.props.userId,
+ type: 'audio',
+ mute: shouldMute
+ });
+
+ this.setState({
+ isMuted: shouldMute,
+ error: null,
+ ended: null
+ });
+ }
+
+ onToggleRemoteMute(message) {
+ if (message.type === 'video') {
+ this.setState({
+ isRemotePaused: message.mute
+ });
+ } else {
+ this.setState({isRemoteMuted: message.mute, error: null, ended: null});
+ }
+ }
+
+ toggleIcons() {
+ const icons = this.refs.icons;
+ if (icons) {
+ icons.classList.toggle('hidden');
+ icons.classList.toggle('active');
+ }
+ }
+
+ renderButtons() {
+ let buttons;
+ if (this.state.isCalling) {
+ buttons = (
+ <svg
+ id='cancel'
+ className='webrtc-icons__cancel'
+ xmlns='http://www.w3.org/2000/svg'
+ width='48'
+ height='48'
+ viewBox='-10 -10 68 68'
+ onClick={() => this.handleCancelOffer()}
+ >
+ <circle
+ cx='24'
+ cy='24'
+ r='34'
+ >
+ <title>
+ <FormattedMessage
+ id='webrtc.cancel'
+ defaultMessage='Cancel Call'
+ />
+ </title>
+ </circle>
+ <path
+ transform='scale(0.8), translate(6,10)'
+ d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
+ fill='white'
+ />
+ </svg>
+ );
+ } else if (!this.state.callInProgress && this.state.localMediaLoaded) {
+ buttons = (
+ <svg
+ id='call'
+ className='webrtc-icons__call'
+ xmlns='http://www.w3.org/2000/svg'
+ width='48'
+ height='48'
+ viewBox='-10 -10 68 68'
+ onClick={() => this.handleMakeOffer()}
+ disabled={UserStore.getStatus(this.props.userId) === 'offline'}
+ >
+ <circle
+ cx='24'
+ cy='24'
+ r='34'
+ >
+ <title>
+ <FormattedMessage
+ id='webrtc.call'
+ defaultMessage='Call'
+ />
+ </title>
+ </circle>
+ <path
+ transform='translate(-10,-10)'
+ fill='#fff'
+ d='M29.854,37.627c1.723,1.904 3.679,3.468 5.793,4.684l3.683,-3.334c0.469,-0.424 1.119,-0.517 1.669,-0.302c1.628,0.63 3.331,1.021 5.056,1.174c0.401,0.026 0.795,0.199 1.09,0.525c0.295,0.326 0.433,0.741 0.407,1.153l-0.279,5.593c-0.02,0.418 -0.199,0.817 -0.525,1.112c-0.326,0.296 -0.741,0.434 -1.159,0.413c-6.704,-0.504 -13.238,-3.491 -18.108,-8.87c-4.869,-5.38 -7.192,-12.179 -7.028,-18.899c0.015,-0.413 0.199,-0.817 0.526,-1.113c0.326,-0.295 0.74,-0.433 1.153,-0.407l5.593,0.279c0.407,0.02 0.812,0.193 1.107,0.519c0.29,0.32 0.428,0.735 0.413,1.137c-0.018,1.732 0.202,3.464 0.667,5.147c0.159,0.569 0.003,1.207 -0.466,1.631l-3.683,3.334c1.005,2.219 2.368,4.32 4.091,6.224Z'
+ />
+ </svg>
+ );
+ } else if (this.state.callInProgress) {
+ const onClass = 'on';
+ const offClass = 'off';
+ let audioOnClass = offClass;
+ let audioOffClass = onClass;
+ let videoOnClass = offClass;
+ let videoOffClass = onClass;
+
+ let audioTitle = (
+ <FormattedMessage
+ id='webrtc.mute_audio'
+ defaultMessage='Mute'
+ />
+ );
+
+ let videoTitle = (
+ <FormattedMessage
+ id='webrtc.pause_video'
+ defaultMessage='Turn off Video'
+ />
+ );
+
+ if (this.state.isMuted) {
+ audioOnClass = onClass;
+ audioOffClass = offClass;
+ audioTitle = (
+ <FormattedMessage
+ id='webrtc.unmute_audio'
+ defaultMessage='Unmute'
+ />
+ );
+ }
+
+ if (this.state.isPaused) {
+ videoOnClass = onClass;
+ videoOffClass = offClass;
+ videoTitle = (
+ <FormattedMessage
+ id='webrtc.unpause_video'
+ defaultMessage='Turn on Video'
+ />
+ );
+ }
+
+ buttons = (
+ <div
+ ref='icons'
+ className='webrtc-icons hidden'
+ >
+
+ <svg
+ id='mute-audio'
+ className='webrtc-icons__call'
+ xmlns='http://www.w3.org/2000/svg'
+ width='48'
+ height='48'
+ viewBox='-10 -10 68 68'
+ onClick={() => this.onToggleAudio()}
+ >
+ <circle
+ cx='24'
+ cy='24'
+ r='34'
+ >
+ <title>{audioTitle}</title>
+ </circle>
+ <path
+ className={audioOnClass}
+ transform='scale(0.6), translate(17,18)'
+ d='M38 22h-3.4c0 1.49-.31 2.87-.87 4.1l2.46 2.46C37.33 26.61 38 24.38 38 22zm-8.03.33c0-.11.03-.22.03-.33V10c0-3.32-2.69-6-6-6s-6 2.68-6 6v.37l11.97 11.96zM8.55 6L6 8.55l12.02 12.02v1.44c0 3.31 2.67 6 5.98 6 .45 0 .88-.06 1.3-.15l3.32 3.32c-1.43.66-3 1.03-4.62 1.03-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c1.81-.27 3.53-.9 5.08-1.81L39.45 42 42 39.46 8.55 6z'
+ fill='white'
+ />
+ <path
+ className={audioOffClass}
+ transform='scale(0.6), translate(17,18)'
+ d='M24 28c3.31 0 5.98-2.69 5.98-6L30 10c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z'
+ fill='white'
+ />
+ </svg>
+
+ <svg
+ id='mute-video'
+ className='webrtc-icons__call'
+ xmlns='http://www.w3.org/2000/svg'
+ width='48'
+ height='48'
+ viewBox='-10 -10 68 68'
+ onClick={() => this.onToggleVideo()}
+ >
+ <circle
+ cx='24'
+ cy='24'
+ r='34'
+ >
+ <title>{videoTitle}</title>
+ </circle>
+ <path
+ className={videoOnClass}
+ transform='scale(0.6), translate(17,16)'
+ d='M40 8H15.64l8 8H28v4.36l1.13 1.13L36 16v12.36l7.97 7.97L44 36V12c0-2.21-1.79-4-4-4zM4.55 2L2 4.55l4.01 4.01C4.81 9.24 4 10.52 4 12v24c0 2.21 1.79 4 4 4h29.45l4 4L44 41.46 4.55 2zM12 16h1.45L28 30.55V32H12V16z'
+ fill='white'
+ />
+ <path
+ className={videoOffClass}
+ transform='scale(0.6), translate(17,16)'
+ d='M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zm-4 24l-8-6.4V32H12V16h16v6.4l8-6.4v16z'
+ fill='white'
+ />
+ </svg>
+
+ <svg
+ id='hangup'
+ className='webrtc-icons__cancel'
+ xmlns='http://www.w3.org/2000/svg'
+ width='48'
+ height='48'
+ viewBox='-10 -10 68 68'
+ onClick={() => this.doHangup(false, true)}
+ >
+ <circle
+ cx='24'
+ cy='24'
+ r='34'
+ >
+ <title>
+ <FormattedMessage
+ id='webrtc.hangup'
+ defaultMessage='Hangup'
+ />
+ </title>
+ </circle>
+ <path
+ transform='scale(0.7), translate(11,10)'
+ d='M24 18c-3.21 0-6.3.5-9.2 1.44v6.21c0 .79-.46 1.47-1.12 1.8-1.95.98-3.74 2.23-5.33 3.7-.36.35-.85.57-1.4.57-.55 0-1.05-.22-1.41-.59L.59 26.18c-.37-.37-.59-.87-.59-1.42 0-.55.22-1.05.59-1.42C6.68 17.55 14.93 14 24 14s17.32 3.55 23.41 9.34c.37.36.59.87.59 1.42 0 .55-.22 1.05-.59 1.41l-4.95 4.95c-.36.36-.86.59-1.41.59-.54 0-1.04-.22-1.4-.57-1.59-1.47-3.38-2.72-5.33-3.7-.66-.33-1.12-1.01-1.12-1.8v-6.21C30.3 18.5 27.21 18 24 18z'
+ fill='white'
+ />
+ </svg>
+
+ </div>
+ );
+ }
+
+ return buttons;
+ }
+
+ render() {
+ const currentId = UserStore.getCurrentId();
+ const remoteImage = (<img src={this.state.remoteUserImage}/>);
+ let localImage;
+ let localVideoHidden;
+ let remoteVideoHidden = 'hidden';
+ let error;
+ let remoteMute;
+ let videoClass = '';
+ let localImageHidden = 'webrtc__local-image hidden';
+ let remoteImageHidden = 'webrtc__remote-image';
+
+ if (this.state.error) {
+ error = (
+ <div className='webrtc__error'>
+ <div className='form-group has-error'>
+ <label className='control-label'>{this.state.error}</label>
+ </div>
+ </div>
+ );
+ } else if (this.state.ended) {
+ error = (
+ <div className='webrtc__error'>
+ <div className='form-group'>
+ <label className='control-label'>{this.state.ended}</label>
+ </div>
+ </div>
+ );
+ }
+
+ if (this.state.isRemoteMuted) {
+ remoteMute = (
+ <div className='webrtc__remote-mute'>
+ <svg
+ xmlns='http://www.w3.org/2000/svg'
+ width='60'
+ height='60'
+ viewBox='-10 -10 68 68'
+ >
+ <path
+ className='off'
+ transform='scale(0.6), translate(17,18)'
+ d='M38 22h-3.4c0 1.49-.31 2.87-.87 4.1l2.46 2.46C37.33 26.61 38 24.38 38 22zm-8.03.33c0-.11.03-.22.03-.33V10c0-3.32-2.69-6-6-6s-6 2.68-6 6v.37l11.97 11.96zM8.55 6L6 8.55l12.02 12.02v1.44c0 3.31 2.67 6 5.98 6 .45 0 .88-.06 1.3-.15l3.32 3.32c-1.43.66-3 1.03-4.62 1.03-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c1.81-.27 3.53-.9 5.08-1.81L39.45 42 42 39.46 8.55 6z'
+ fill='white'
+ />
+ <path
+ className='on'
+ transform='scale(0.6), translate(17,18)'
+ d='M24 28c3.31 0 5.98-2.69 5.98-6L30 10c0-3.32-2.68-6-6-6-3.31 0-6 2.68-6 6v12c0 3.31 2.69 6 6 6zm10.6-6c0 6-5.07 10.2-10.6 10.2-5.52 0-10.6-4.2-10.6-10.2H10c0 6.83 5.44 12.47 12 13.44V42h4v-6.56c6.56-.97 12-6.61 12-13.44h-3.4z'
+ fill='white'
+ />
+ </svg>
+ </div>
+ );
+ }
+
+ let searchForm;
+ if (currentId != null) {
+ searchForm = <SearchBox/>;
+ }
+
+ const buttons = this.renderButtons();
+ const calling = this.state.isCalling;
+ let connecting;
+ let audio;
+ if (calling || this.state.isAnswering) {
+ if (calling) {
+ audio = (
+ <audio
+ ref='ring'
+ src={ring}
+ autoPlay={true}
+ />
+ );
+ }
+
+ connecting = (
+ <div className='connecting'>
+ <ConnectingScreen
+ position='absolute'
+ message={this.state.connectingMsg}
+ />
+ {audio}
+ </div>
+ );
+ }
+
+ if (this.state.callInProgress) {
+ if (this.state.isPaused) {
+ localVideoHidden = 'hidden';
+ localImageHidden = 'webrtc__local-image';
+ localImage = (<img src={this.state.currentUserImage}/>);
+ }
+
+ if (this.state.isRemotePaused) {
+ remoteVideoHidden = 'hidden';
+ remoteImageHidden = 'webrtc__remote-image';
+ } else {
+ remoteVideoHidden = '';
+ remoteImageHidden = 'webrtc__remote-image hidden';
+ }
+ } else {
+ videoClass = 'small';
+ }
+
+ return (
+ <div className='post-right__container'>
+ <div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
+ <div className='sidebar-right__body'>
+ <WebrtcHeader
+ username={Utils.displayUsername(this.props.userId)}
+ onClose={this.handleClose}
+ toggleSize={this.props.toggleSize}
+ />
+ <div className='post-right__scroll'>
+ <div
+ id='videos'
+ className={videoClass}
+ >
+ {remoteMute}
+ <div
+ id='main-video'
+ className={remoteVideoHidden}
+ autoPlay={true}
+ >
+ <video
+ ref='main-video'
+ autoPlay={true}
+ />
+ </div>
+ <div
+ id='local-video'
+ className={localVideoHidden}
+ >
+ <video
+ ref='local-video'
+ autoPlay={true}
+ muted={true}
+ />
+ </div>
+ <div className={remoteImageHidden}>
+ {remoteImage}
+ </div>
+ <div className={localImageHidden}>
+ {localImage}
+ </div>
+ </div>
+ {error}
+ {connecting}
+ <div className='webrtc-buttons'>
+ {buttons}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+WebrtcController.propTypes = {
+ currentUser: React.PropTypes.object,
+ userId: React.PropTypes.string.isRequired,
+ isCaller: React.PropTypes.bool.isRequired,
+ expanded: React.PropTypes.bool.isRequired,
+ toggleSize: React.PropTypes.function
+}; \ No newline at end of file