* 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)
Этот коммит содержится в:
enahum
2016-09-16 15:35:13 -03:00
коммит произвёл GitHub
родитель df2d61d941
Коммит 781ff323db
45 изменённых файлов: 4902 добавлений и 90 удалений

Просмотреть файл

@@ -0,0 +1,100 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Constants from 'utils/constants.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import React from 'react';
export default class WebrtcHeader extends React.Component {
render() {
const title = (
<FormattedMessage
id='webrtc.header'
defaultMessage='Call with {username}'
values={{
username: this.props.username
}}
/>
);
const closeSidebarTooltip = (
<Tooltip id='closeSidebarTooltip'>
<FormattedMessage
id='rhs_header.closeTooltip'
defaultMessage='Close Sidebar'
/>
</Tooltip>
);
const expandSidebarTooltip = (
<Tooltip id='expandSidebarTooltip'>
<FormattedMessage
id='rhs_header.expandTooltip'
defaultMessage='Expand Sidebar'
/>
</Tooltip>
);
const shrinkSidebarTooltip = (
<Tooltip id='shrinkSidebarTooltip'>
<FormattedMessage
id='rhs_header.expandTooltip'
defaultMessage='Shrink Sidebar'
/>
</Tooltip>
);
return (
<div className='sidebar--right__header'>
<span className='sidebar--right__title'>{title}</span>
<div className='pull-right'>
<button
type='button'
className='sidebar--right__expand'
aria-label='Expand'
onClick={this.props.toggleSize}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={expandSidebarTooltip}
>
<i className='fa fa-expand'/>
</OverlayTrigger>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={shrinkSidebarTooltip}
>
<i className='fa fa-compress'/>
</OverlayTrigger>
</button>
<button
type='button'
className='sidebar--right__close'
aria-label='Close'
title='Close'
onClick={this.props.onClose}
>
<OverlayTrigger
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='top'
overlay={closeSidebarTooltip}
>
<i className='fa fa-sign-out'/>
</OverlayTrigger>
</button>
</div>
</div>
);
}
}
WebrtcHeader.propTypes = {
username: React.PropTypes.string.isRequired,
onClose: React.PropTypes.func.isRequired,
toggleSize: React.PropTypes.function
};

Просмотреть файл

@@ -0,0 +1,320 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Client from 'client/web_client.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as WebrtcActions from 'actions/webrtc_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import ring from 'images/ring.mp3';
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
export default class WebrtcNotification extends React.Component {
constructor() {
super();
this.mounted = false;
this.closeNotification = this.closeNotification.bind(this);
this.onIncomingCall = this.onIncomingCall.bind(this);
this.onCancelCall = this.onCancelCall.bind(this);
this.onRhs = this.onRhs.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleAnswer = this.handleAnswer.bind(this);
this.handleTimeout = this.handleTimeout.bind(this);
this.stopRinging = this.stopRinging.bind(this);
this.closeRightHandSide = this.closeRightHandSide.bind(this);
this.state = {
userCalling: null,
rhsOpened: false
};
}
componentDidMount() {
WebrtcStore.addNotifyListener(this.onIncomingCall);
WebrtcStore.addChangedListener(this.onCancelCall);
WebrtcStore.addRhsChangedListener(this.onRhs);
this.mounted = true;
}
componentWillUnmount() {
WebrtcStore.removeNotifyListener(this.onIncomingCall);
WebrtcStore.removeChangedListener(this.onCancelCall);
WebrtcStore.removeRhsChangedListener(this.onRhs);
if (this.refs.ring) {
this.refs.ring.removeListener('ended', this.handleTimeout);
}
this.mounted = false;
}
componentDidUpdate() {
if (this.state.userCalling) {
this.refs.ring.addEventListener('ended', this.handleTimeout);
}
}
closeNotification() {
this.setState({
userCalling: null
});
}
stopRinging() {
if (this.refs.ring) {
this.refs.ring.pause();
this.refs.ring.currentTime = 0;
}
this.setState({userCalling: null});
}
closeRightHandSide(e) {
e.preventDefault();
GlobalActions.emitCloseRightHandSide();
}
onIncomingCall(incoming) {
if (this.mounted) {
const userId = incoming.from_user_id;
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
const featureEnabled = Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
if (featureEnabled) {
if (WebrtcStore.isBusy()) {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.BUSY,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
} else if (userMedia) {
WebrtcStore.setVideoCallWith(userId);
this.setState({
userCalling: UserStore.getProfile(userId)
});
} else {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.UNSUPPORTED,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
}
} else {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.DISABLED,
from_user_id: UserStore.getCurrentId(),
to_user_id: userId
});
this.stopRinging();
}
}
}
onCancelCall(message) {
if (message && message.action !== WebrtcActionTypes.CANCEL) {
return;
} else if (message && message.action === WebrtcActionTypes.CANCEL && this.state.userCalling && message.from_user_id !== this.state.userCalling.id) {
return;
}
WebrtcStore.setVideoCallWith(null);
this.closeNotification();
}
onRhs(rhsOpened) {
this.setState({rhsOpened});
}
handleTimeout() {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.NO_ANSWER,
from_user_id: UserStore.getCurrentId(),
to_user_id: this.state.userCalling.id
});
this.onCancelCall();
}
handleAnswer(e) {
if (e) {
e.preventDefault();
}
const caller = this.state.userCalling;
if (caller) {
const callerId = caller.id;
const currentUserId = UserStore.getCurrentId();
const message = {
action: WebrtcActionTypes.ANSWER,
from_user_id: currentUserId,
to_user_id: callerId
};
GlobalActions.emitCloseRightHandSide();
WebrtcActions.initWebrtc(callerId, false);
WebSocketClient.sendMessage('webrtc', message);
// delay till next tick (this will give time to listen for events
setTimeout(() => {
//we switch from and to user to handle the event locally
message.from_user_id = callerId;
message.to_user_id = currentUserId;
WebrtcActions.handle(message);
}, 0);
this.closeNotification();
}
}
handleClose(e) {
if (e) {
e.preventDefault();
}
if (this.state.userCalling) {
WebSocketClient.sendMessage('webrtc', {
action: WebrtcActionTypes.DECLINE,
from_user_id: UserStore.getCurrentId(),
to_user_id: this.state.userCalling.id
});
}
this.onCancelCall();
}
render() {
const user = this.state.userCalling;
if (user) {
const username = Utils.displayUsername(user.id);
const profileImgSrc = Client.getUsersRoute() + '/' + user.id + '/image?time=' + (user.update_at || new Date().getTime());
const profileImg = (
<img
className='user-popover__image'
src={profileImgSrc}
height='128'
width='128'
key='user-popover-image'
/>
);
const answerBtn = (
<svg
className='webrtc-icons__pickup'
xmlns='http://www.w3.org/2000/svg'
width='48'
height='48'
viewBox='-10 -10 68 68'
onClick={this.handleAnswer}
>
<circle
cx='24'
cy='24'
r='34'
>
<title>
<FormattedMessage
id='webrtc.notification.answer'
defaultMessage='Answer'
/>
</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>
);
const rejectBtn = (
<svg
className='webrtc-icons__cancel'
xmlns='http://www.w3.org/2000/svg'
width='48'
height='48'
viewBox='-10 -10 68 68'
onClick={this.handleClose}
>
<circle
cx='24'
cy='24'
r='34'
>
<title>
<FormattedMessage
id='webrtc.notification.decline'
defaultMessage='Decline'
/>
</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>
);
const msg = (
<div>
<FormattedMessage
id='webrtc.notification.incoming_call'
defaultMessage='{username} is calling you.'
values={{
username
}}
/>
<div
className='webrtc-buttons webrtc-icons active'
style={{marginTop: '5px'}}
>
{answerBtn}
{rejectBtn}
</div>
</div>
);
return (
<div className='webrtc-notification'>
<audio
ref='ring'
src={ring}
autoPlay={true}
/>
<div>
{profileImg}
</div>
{msg}
</div>
);
} else if (this.state.rhsOpened && WebrtcStore.isBusy()) {
return (
<div
className='webrtc__notification--rhs'
onClick={this.closeRightHandSide}
>
<i className='fa fa-phone'/>
<FormattedMessage
id='webrtc.notification.returnToCall'
defaultMessage='Return to ongoing call with {username}'
values={{
username: Utils.displayUsername(WebrtcStore.getVideoCallWith())
}}
/>
</div>
);
}
return <div/>;
}
}

Просмотреть файл

@@ -0,0 +1,133 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import WebrtcController from '../webrtc_controller.jsx';
import UserStore from 'stores/user_store.jsx';
import WebrtcStore from 'stores/webrtc_store.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
export default class SidebarRight extends React.Component {
constructor(props) {
super(props);
this.plScrolledToBottom = true;
this.onShrink = this.onShrink.bind(this);
this.toggleSize = this.toggleSize.bind(this);
this.onInitializeVideoCall = this.onInitializeVideoCall.bind(this);
this.doStrangeThings = this.doStrangeThings.bind(this);
this.state = {
expanded: false,
currentUser: UserStore.getCurrentUser(),
videoCallVisible: false,
isCaller: false,
videoCallWithUserId: null
};
}
componentDidMount() {
WebrtcStore.addInitListener(this.onInitializeVideoCall);
this.doStrangeThings();
}
componentWillUnmount() {
WebrtcStore.removeInitListener(this.onInitializeVideoCall);
}
shouldComponentUpdate(nextProps, nextState) {
return !Utils.areObjectsEqual(nextState, this.state);
}
doStrangeThings() {
// We should have a better way to do this stuff
// Hence the function name.
$('.app__body .inner-wrap').removeClass('move--right');
$('.app__body .inner-wrap').addClass('webrtc--show');
$('.app__body .sidebar--left').removeClass('move--right');
$('.app__body .webrtc').addClass('webrtc--show');
//$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
if (!this.state.videoCallVisible) {
$('.app__body .inner-wrap').removeClass('webrtc--show').removeClass('move--right');
$('.app__body .webrtc').removeClass('webrtc--show');
return (
<div></div>
);
}
return null;
}
componentDidUpdate() {
this.doStrangeThings();
}
onShrink() {
this.setState({expanded: false});
}
toggleSize(e) {
if (e) {
e.preventDefault();
}
this.setState({expanded: !this.state.expanded});
}
onInitializeVideoCall(userId, isCaller) {
let expanded = this.state.expanded;
if (userId === null) {
expanded = false;
}
this.setState({
videoCallVisible: (userId !== null),
isCaller,
videoCallWithUserId: userId,
expanded
});
if (userId !== null) {
this.forceUpdate();
}
}
render() {
let content = null;
let expandedClass = '';
if (this.state.expanded) {
expandedClass = 'sidebar--right--expanded';
}
if (this.state.videoCallVisible) {
content = (
<WebrtcController
currentUser={this.state.currentUser}
userId={this.state.videoCallWithUserId}
isCaller={this.state.isCaller}
expanded={this.state.expanded}
toggleSize={this.toggleSize}
/>
);
}
return (
<div
className={'sidebar--right webrtc ' + expandedClass}
id='sidebar-webrtc'
>
<div
onClick={this.onShrink}
className='sidebar--right__bg'
/>
<div className='sidebar-right-container'>
{content}
</div>
</div>
);
}
}

1214
webapp/components/webrtc/webrtc_controller.jsx Обычный файл

Разница между файлами не показана из-за своего большого размера Загрузить разницу