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)
Этот коммит содержится в:
100
webapp/components/webrtc/components/webrtc_header.jsx
Обычный файл
100
webapp/components/webrtc/components/webrtc_header.jsx
Обычный файл
@@ -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
|
||||
};
|
||||
320
webapp/components/webrtc/components/webrtc_notification.jsx
Обычный файл
320
webapp/components/webrtc/components/webrtc_notification.jsx
Обычный файл
@@ -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/>;
|
||||
}
|
||||
}
|
||||
133
webapp/components/webrtc/components/webrtc_sidebar.jsx
Обычный файл
133
webapp/components/webrtc/components/webrtc_sidebar.jsx
Обычный файл
@@ -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
Обычный файл
1214
webapp/components/webrtc/webrtc_controller.jsx
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Ссылка в новой задаче
Block a user