Merge branch 'feat-cleanup' into develop
commit
94b1db5789
|
|
@ -2,6 +2,7 @@
|
||||||
"plugins":
|
"plugins":
|
||||||
[
|
[
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
|
"jsx-control-statements",
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/plugin-proposal-class-properties",
|
||||||
"@babel/plugin-transform-runtime"
|
"@babel/plugin-transform-runtime"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ module.exports =
|
||||||
extends:
|
extends:
|
||||||
[
|
[
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:react/recommended'
|
'plugin:react/recommended',
|
||||||
|
'plugin:jsx-control-statements/recommended'
|
||||||
],
|
],
|
||||||
settings:
|
settings:
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const RoomContext = React.createContext();
|
||||||
|
|
||||||
|
export default RoomContext;
|
||||||
|
|
||||||
|
export function withRoomContext(Component)
|
||||||
|
{
|
||||||
|
return (props) => ( // eslint-disable-line react/display-name
|
||||||
|
<RoomContext.Consumer>
|
||||||
|
{(roomClient) => <Component {...props} roomClient={roomClient} />}
|
||||||
|
</RoomContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { compose } from 'redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import marked from 'marked';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import scrollToBottom from './scrollToBottom';
|
|
||||||
|
|
||||||
const linkRenderer = new marked.Renderer();
|
|
||||||
|
|
||||||
linkRenderer.link = (href, title, text) =>
|
|
||||||
{
|
|
||||||
title = title ? title : href;
|
|
||||||
text = text ? text : href;
|
|
||||||
|
|
||||||
return (`<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`);
|
|
||||||
};
|
|
||||||
|
|
||||||
class MessageList extends Component
|
|
||||||
{
|
|
||||||
getTimeString(time)
|
|
||||||
{
|
|
||||||
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
const {
|
|
||||||
chatmessages
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-component='MessageList' id='messages'>
|
|
||||||
{ chatmessages.length > 0 ?
|
|
||||||
chatmessages.map((message, i) =>
|
|
||||||
{
|
|
||||||
const messageTime = new Date(message.time);
|
|
||||||
|
|
||||||
const picture = (message.sender === 'response' ?
|
|
||||||
message.picture : this.props.myPicture) || 'resources/images/avatar-empty.jpeg';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='message' key={i}>
|
|
||||||
<div className={message.sender}>
|
|
||||||
<img className='message-avatar' src={picture} />
|
|
||||||
|
|
||||||
<div className='message-content'>
|
|
||||||
<div
|
|
||||||
className='message-text'
|
|
||||||
// eslint-disable-next-line react/no-danger
|
|
||||||
dangerouslySetInnerHTML={{ __html : marked.parse(
|
|
||||||
message.text,
|
|
||||||
{ sanitize: true, renderer: linkRenderer }
|
|
||||||
) }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className='message-time'>
|
|
||||||
{message.name} - {this.getTimeString(messageTime)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
:<div className='empty'>
|
|
||||||
<p>No one has said anything yet...</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageList.propTypes =
|
|
||||||
{
|
|
||||||
chatmessages : PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
myPicture : PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
chatmessages : state.chatmessages,
|
|
||||||
myPicture : state.me.picture
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageListContainer = compose(
|
|
||||||
connect(mapStateToProps),
|
|
||||||
scrollToBottom()
|
|
||||||
)(MessageList);
|
|
||||||
|
|
||||||
export default MessageListContainer;
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as stateActions from '../redux/stateActions';
|
import * as stateActions from '../../redux/stateActions';
|
||||||
|
|
||||||
class HiddenPeers extends Component
|
class HiddenPeers extends Component
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +29,7 @@ class HiddenPeers extends Component
|
||||||
this.timeout = setTimeout(() =>
|
this.timeout = setTimeout(() =>
|
||||||
{
|
{
|
||||||
this.setState({ className: '' });
|
this.setState({ className: '' });
|
||||||
}, 2000);
|
}, 500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,20 +42,13 @@ class HiddenPeers extends Component
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div data-component='HiddenPeers'>
|
||||||
data-component='HiddenPeers'
|
|
||||||
className={this.state.className}
|
|
||||||
onMouseOver={this.handleMouseOver}
|
|
||||||
onMouseOut={this.handleMouseOut}
|
|
||||||
>
|
|
||||||
<div data-component='HiddenPeersView'>
|
|
||||||
<div className={classnames('view-container', this.state.className)} onClick={() => openUsersTab()}>
|
<div className={classnames('view-container', this.state.className)} onClick={() => openUsersTab()}>
|
||||||
<p>+{hiddenPeersCount} <br /> participant
|
<p>+{hiddenPeersCount} <br /> participant
|
||||||
{(hiddenPeersCount === 1) ? null : 's'}
|
{(hiddenPeersCount === 1) ? null : 's'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ import ReactTooltip from 'react-tooltip';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { getDeviceInfo } from 'mediasoup-client';
|
import { getDeviceInfo } from 'mediasoup-client';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as requestActions from '../redux/requestActions';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import PeerView from './PeerView';
|
import PeerView from '../VideoContainers/PeerView';
|
||||||
import ScreenView from './ScreenView';
|
import ScreenView from '../VideoContainers/ScreenView';
|
||||||
|
|
||||||
class Me extends React.Component
|
class Me extends React.Component
|
||||||
{
|
{
|
||||||
|
|
@ -46,17 +46,13 @@ class Me extends React.Component
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
|
roomClient,
|
||||||
connected,
|
connected,
|
||||||
me,
|
me,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
micProducer,
|
micProducer,
|
||||||
webcamProducer,
|
webcamProducer,
|
||||||
screenProducer,
|
screenProducer
|
||||||
onChangeDisplayName,
|
|
||||||
onMuteMic,
|
|
||||||
onUnmuteMic,
|
|
||||||
onEnableWebcam,
|
|
||||||
onDisableWebcam
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let micState;
|
let micState;
|
||||||
|
|
@ -107,7 +103,7 @@ class Me extends React.Component
|
||||||
onMouseOut={this.handleMouseOut}
|
onMouseOut={this.handleMouseOut}
|
||||||
>
|
>
|
||||||
<div className={classnames('view-container', 'webcam')}>
|
<div className={classnames('view-container', 'webcam')}>
|
||||||
{connected ?
|
<If condition={connected}>
|
||||||
<div className={classnames('controls', 'visible')}>
|
<div className={classnames('controls', 'visible')}>
|
||||||
<div
|
<div
|
||||||
data-tip='keyboard shortcut: ‘m‘'
|
data-tip='keyboard shortcut: ‘m‘'
|
||||||
|
|
@ -120,7 +116,9 @@ class Me extends React.Component
|
||||||
})}
|
})}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
micState === 'on' ? onMuteMic() : onUnmuteMic();
|
micState === 'on' ?
|
||||||
|
roomClient.muteMic() :
|
||||||
|
roomClient.unmuteMic();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ReactTooltip
|
<ReactTooltip
|
||||||
|
|
@ -134,12 +132,13 @@ class Me extends React.Component
|
||||||
})}
|
})}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
{
|
{
|
||||||
webcamState === 'on' ? onDisableWebcam() : onEnableWebcam();
|
webcamState === 'on' ?
|
||||||
|
roomClient.disableWebcam() :
|
||||||
|
roomClient.enableWebcam();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
<PeerView
|
<PeerView
|
||||||
isMe
|
isMe
|
||||||
|
|
@ -151,11 +150,14 @@ class Me extends React.Component
|
||||||
videoVisible={videoVisible}
|
videoVisible={videoVisible}
|
||||||
audioCodec={micProducer ? micProducer.codec : null}
|
audioCodec={micProducer ? micProducer.codec : null}
|
||||||
videoCodec={webcamProducer ? webcamProducer.codec : null}
|
videoCodec={webcamProducer ? webcamProducer.codec : null}
|
||||||
onChangeDisplayName={(displayName) => onChangeDisplayName(displayName)}
|
onChangeDisplayName={(displayName) =>
|
||||||
|
{
|
||||||
|
roomClient.changeDisplayName(displayName);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{screenProducer ?
|
<If condition={screenProducer}>
|
||||||
<div className={classnames('view-container', 'screen')}>
|
<div className={classnames('view-container', 'screen')}>
|
||||||
<ScreenView
|
<ScreenView
|
||||||
isMe
|
isMe
|
||||||
|
|
@ -165,8 +167,7 @@ class Me extends React.Component
|
||||||
screenCodec={screenProducer ? screenProducer.codec : null}
|
screenCodec={screenProducer ? screenProducer.codec : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -204,17 +205,13 @@ class Me extends React.Component
|
||||||
|
|
||||||
Me.propTypes =
|
Me.propTypes =
|
||||||
{
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
connected : PropTypes.bool.isRequired,
|
connected : PropTypes.bool.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
me : appPropTypes.Me.isRequired,
|
me : appPropTypes.Me.isRequired,
|
||||||
micProducer : appPropTypes.Producer,
|
micProducer : appPropTypes.Producer,
|
||||||
webcamProducer : appPropTypes.Producer,
|
webcamProducer : appPropTypes.Producer,
|
||||||
screenProducer : appPropTypes.Producer,
|
screenProducer : appPropTypes.Producer
|
||||||
onChangeDisplayName : PropTypes.func.isRequired,
|
|
||||||
onMuteMic : PropTypes.func.isRequired,
|
|
||||||
onUnmuteMic : PropTypes.func.isRequired,
|
|
||||||
onEnableWebcam : PropTypes.func.isRequired,
|
|
||||||
onDisableWebcam : PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
|
|
@ -236,23 +233,8 @@ const mapStateToProps = (state) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const MeContainer = withRoomContext(connect(
|
||||||
{
|
mapStateToProps
|
||||||
return {
|
)(Me));
|
||||||
onChangeDisplayName : (displayName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.changeDisplayName(displayName));
|
|
||||||
},
|
|
||||||
onMuteMic : () => dispatch(requestActions.muteMic()),
|
|
||||||
onUnmuteMic : () => dispatch(requestActions.unmuteMic()),
|
|
||||||
onEnableWebcam : () => dispatch(requestActions.enableWebcam()),
|
|
||||||
onDisableWebcam : () => dispatch(requestActions.disableWebcam())
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const MeContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Me);
|
|
||||||
|
|
||||||
export default MeContainer;
|
export default MeContainer;
|
||||||
|
|
@ -2,11 +2,11 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as requestActions from '../redux/requestActions';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import * as stateActions from '../redux/stateActions';
|
import * as stateActions from '../../redux/stateActions';
|
||||||
import PeerView from './PeerView';
|
import PeerView from '../VideoContainers/PeerView';
|
||||||
import ScreenView from './ScreenView';
|
import ScreenView from '../VideoContainers/ScreenView';
|
||||||
|
|
||||||
class Peer extends Component
|
class Peer extends Component
|
||||||
{
|
{
|
||||||
|
|
@ -31,13 +31,12 @@ class Peer extends Component
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
|
roomClient,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
peer,
|
peer,
|
||||||
micConsumer,
|
micConsumer,
|
||||||
webcamConsumer,
|
webcamConsumer,
|
||||||
screenConsumer,
|
screenConsumer,
|
||||||
onMuteMic,
|
|
||||||
onUnmuteMic,
|
|
||||||
toggleConsumerFullscreen,
|
toggleConsumerFullscreen,
|
||||||
toggleConsumerWindow,
|
toggleConsumerWindow,
|
||||||
style,
|
style,
|
||||||
|
|
@ -81,23 +80,21 @@ class Peer extends Component
|
||||||
onMouseOver={this.handleMouseOver}
|
onMouseOver={this.handleMouseOver}
|
||||||
onMouseOut={this.handleMouseOut}
|
onMouseOut={this.handleMouseOut}
|
||||||
>
|
>
|
||||||
{videoVisible && !webcamConsumer.supported ?
|
<If condition={videoVisible && !webcamConsumer.supported}>
|
||||||
<div className='incompatible-video'>
|
<div className='incompatible-video'>
|
||||||
<p>incompatible video</p>
|
<p>incompatible video</p>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
{!videoVisible ?
|
<If condition={!videoVisible}>
|
||||||
<div className='paused-video'>
|
<div className='paused-video'>
|
||||||
<p>this video is paused</p>
|
<p>this video is paused</p>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
<div className={classnames('view-container', 'webcam')} style={style}>
|
<div className={classnames('view-container', 'webcam')} style={style}>
|
||||||
<div className='indicators'>
|
<div className='indicators'>
|
||||||
{peer.raiseHandState ?
|
<If condition={peer.raiseHandState}>
|
||||||
<div className={
|
<div className={
|
||||||
classnames(
|
classnames(
|
||||||
'icon', 'raise-hand', {
|
'icon', 'raise-hand', {
|
||||||
|
|
@ -107,8 +104,7 @@ class Peer extends Component
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames('controls', {
|
className={classnames('controls', {
|
||||||
|
|
@ -124,7 +120,9 @@ class Peer extends Component
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
|
micEnabled ?
|
||||||
|
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
|
||||||
|
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -141,7 +139,9 @@ class Peer extends Component
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'fullscreen')}
|
className={classnames('button', 'fullscreen', {
|
||||||
|
disabled : !videoVisible
|
||||||
|
})}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -162,7 +162,7 @@ class Peer extends Component
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{screenConsumer ?
|
<If condition={screenConsumer}>
|
||||||
<div className={classnames('view-container', 'screen')} style={style}>
|
<div className={classnames('view-container', 'screen')} style={style}>
|
||||||
<div
|
<div
|
||||||
className={classnames('controls', {
|
className={classnames('controls', {
|
||||||
|
|
@ -170,7 +170,10 @@ class Peer extends Component
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'newwindow')}
|
className={classnames('button', 'newwindow', {
|
||||||
|
disabled : !screenVisible ||
|
||||||
|
(windowConsumer === screenConsumer.id)
|
||||||
|
})}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -179,7 +182,9 @@ class Peer extends Component
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'fullscreen')}
|
className={classnames('button', 'fullscreen', {
|
||||||
|
disabled : !screenVisible
|
||||||
|
})}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -195,8 +200,7 @@ class Peer extends Component
|
||||||
screenCodec={screenConsumer ? screenConsumer.codec : null}
|
screenCodec={screenConsumer ? screenConsumer.codec : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -204,14 +208,13 @@ class Peer extends Component
|
||||||
|
|
||||||
Peer.propTypes =
|
Peer.propTypes =
|
||||||
{
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peer : appPropTypes.Peer.isRequired,
|
peer : appPropTypes.Peer.isRequired,
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer,
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
screenConsumer : appPropTypes.Consumer,
|
screenConsumer : appPropTypes.Consumer,
|
||||||
windowConsumer : PropTypes.number,
|
windowConsumer : PropTypes.number,
|
||||||
onMuteMic : PropTypes.func.isRequired,
|
|
||||||
onUnmuteMic : PropTypes.func.isRequired,
|
|
||||||
streamDimensions : PropTypes.object,
|
streamDimensions : PropTypes.object,
|
||||||
style : PropTypes.object,
|
style : PropTypes.object,
|
||||||
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
toggleConsumerFullscreen : PropTypes.func.isRequired,
|
||||||
|
|
@ -242,14 +245,6 @@ const mapStateToProps = (state, { name }) =>
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const mapDispatchToProps = (dispatch) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
onMuteMic : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.mutePeerAudio(peerName));
|
|
||||||
},
|
|
||||||
onUnmuteMic : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.unmutePeerAudio(peerName));
|
|
||||||
},
|
|
||||||
toggleConsumerFullscreen : (consumer) =>
|
toggleConsumerFullscreen : (consumer) =>
|
||||||
{
|
{
|
||||||
if (consumer)
|
if (consumer)
|
||||||
|
|
@ -263,9 +258,9 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const PeerContainer = connect(
|
const PeerContainer = withRoomContext(connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(Peer);
|
)(Peer));
|
||||||
|
|
||||||
export default PeerContainer;
|
export default PeerContainer;
|
||||||
|
|
@ -2,9 +2,9 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as requestActions from '../redux/requestActions';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import FullScreen from './FullScreen';
|
import FullScreen from '../FullScreen';
|
||||||
|
|
||||||
class Sidebar extends Component
|
class Sidebar extends Component
|
||||||
{
|
{
|
||||||
|
|
@ -56,8 +56,10 @@ class Sidebar extends Component
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
toolbarsVisible, me, screenProducer, onLogin, onShareScreen,
|
roomClient,
|
||||||
onUnShareScreen, onNeedExtension, onLeaveMeeting, onLogout, onToggleHand
|
toolbarsVisible,
|
||||||
|
me,
|
||||||
|
screenProducer
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let screenState;
|
let screenState;
|
||||||
|
|
@ -91,7 +93,7 @@ class Sidebar extends Component
|
||||||
})}
|
})}
|
||||||
data-component='Sidebar'
|
data-component='Sidebar'
|
||||||
>
|
>
|
||||||
{this.fullscreen.fullscreenEnabled && (
|
<If condition={this.fullscreen.fullscreenEnabled}>
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'fullscreen', {
|
className={classnames('button', 'fullscreen', {
|
||||||
on : this.state.fullscreen
|
on : this.state.fullscreen
|
||||||
|
|
@ -101,7 +103,7 @@ class Sidebar extends Component
|
||||||
data-place='right'
|
data-place='right'
|
||||||
data-type='dark'
|
data-type='dark'
|
||||||
/>
|
/>
|
||||||
)}
|
</If>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'screen', screenState)}
|
className={classnames('button', 'screen', screenState)}
|
||||||
|
|
@ -114,17 +116,17 @@ class Sidebar extends Component
|
||||||
{
|
{
|
||||||
case 'on':
|
case 'on':
|
||||||
{
|
{
|
||||||
onUnShareScreen();
|
roomClient.disableScreenSharing();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'off':
|
case 'off':
|
||||||
{
|
{
|
||||||
onShareScreen();
|
roomClient.enableScreenSharing();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'need-extension':
|
case 'need-extension':
|
||||||
{
|
{
|
||||||
onNeedExtension();
|
roomClient.installExtension();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -135,25 +137,30 @@ class Sidebar extends Component
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{me.loginEnabled && (me.loggedIn ? (
|
<If condition={me.loginEnabled}>
|
||||||
|
<Choose>
|
||||||
|
<When condition={me.loggedIn}>
|
||||||
<div
|
<div
|
||||||
className='button logout'
|
className='button logout'
|
||||||
data-tip='Logout'
|
data-tip='Logout'
|
||||||
data-place='right'
|
data-place='right'
|
||||||
data-type='dark'
|
data-type='dark'
|
||||||
onClick={onLogout}
|
onClick={() => roomClient.logout()}
|
||||||
>
|
>
|
||||||
<img src={me.picture || 'resources/images/avatar-empty.jpeg'} />
|
<img src={me.picture || 'resources/images/avatar-empty.jpeg'} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</When>
|
||||||
|
<Otherwise>
|
||||||
<div
|
<div
|
||||||
className='button login off'
|
className='button login off'
|
||||||
data-tip='Login'
|
data-tip='Login'
|
||||||
data-place='right'
|
data-place='right'
|
||||||
data-type='dark'
|
data-type='dark'
|
||||||
onClick={onLogin}
|
onClick={() => roomClient.login()}
|
||||||
/>
|
/>
|
||||||
))}
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
</If>
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'raise-hand', {
|
className={classnames('button', 'raise-hand', {
|
||||||
on : me.raiseHand,
|
on : me.raiseHand,
|
||||||
|
|
@ -162,7 +169,7 @@ class Sidebar extends Component
|
||||||
data-tip='Raise hand'
|
data-tip='Raise hand'
|
||||||
data-place='right'
|
data-place='right'
|
||||||
data-type='dark'
|
data-type='dark'
|
||||||
onClick={() => onToggleHand(!me.raiseHand)}
|
onClick={() => roomClient.sendRaiseHandState(!me.raiseHand)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -170,7 +177,7 @@ class Sidebar extends Component
|
||||||
data-tip='Leave meeting'
|
data-tip='Leave meeting'
|
||||||
data-place='right'
|
data-place='right'
|
||||||
data-type='dark'
|
data-type='dark'
|
||||||
onClick={() => onLeaveMeeting()}
|
onClick={() => roomClient.close()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -178,15 +185,9 @@ class Sidebar extends Component
|
||||||
}
|
}
|
||||||
|
|
||||||
Sidebar.propTypes = {
|
Sidebar.propTypes = {
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
toolbarsVisible : PropTypes.bool.isRequired,
|
toolbarsVisible : PropTypes.bool.isRequired,
|
||||||
me : appPropTypes.Me.isRequired,
|
me : appPropTypes.Me.isRequired,
|
||||||
onShareScreen : PropTypes.func.isRequired,
|
|
||||||
onUnShareScreen : PropTypes.func.isRequired,
|
|
||||||
onNeedExtension : PropTypes.func.isRequired,
|
|
||||||
onToggleHand : PropTypes.func.isRequired,
|
|
||||||
onLeaveMeeting : PropTypes.func.isRequired,
|
|
||||||
onLogin : PropTypes.func.isRequired,
|
|
||||||
onLogout : PropTypes.func.isRequired,
|
|
||||||
screenProducer : appPropTypes.Producer
|
screenProducer : appPropTypes.Producer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -198,17 +199,6 @@ const mapStateToProps = (state) =>
|
||||||
me : state.me
|
me : state.me
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
export default withRoomContext(connect(
|
||||||
onLeaveMeeting : requestActions.leaveRoom,
|
mapStateToProps
|
||||||
onShareScreen : requestActions.enableScreenSharing,
|
)(Sidebar));
|
||||||
onUnShareScreen : requestActions.disableScreenSharing,
|
|
||||||
onNeedExtension : requestActions.installExtension,
|
|
||||||
onToggleHand : requestActions.toggleHand,
|
|
||||||
onLogin : requestActions.userLogin,
|
|
||||||
onLogout : requestActions.userLogout
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Sidebar);
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
import React, { Component, Fragment } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import magnet from 'magnet-uri';
|
|
||||||
import WebTorrent from 'webtorrent';
|
|
||||||
import * as requestActions from '../../redux/requestActions';
|
|
||||||
import { saveAs } from 'file-saver/FileSaver';
|
|
||||||
import { client } from './index';
|
|
||||||
|
|
||||||
const DEFAULT_PICTURE = 'resources/images/avatar-empty.jpeg';
|
|
||||||
|
|
||||||
class FileEntry extends Component
|
|
||||||
{
|
|
||||||
state = {
|
|
||||||
active : false,
|
|
||||||
numPeers : 0,
|
|
||||||
progress : 0,
|
|
||||||
files : null
|
|
||||||
};
|
|
||||||
|
|
||||||
saveFile = (file) =>
|
|
||||||
{
|
|
||||||
file.getBlob((err, blob) =>
|
|
||||||
{
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
return this.props.notify({
|
|
||||||
text : 'An error occurred while saving a file'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
saveAs(blob, file.name);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTorrent = (torrent) =>
|
|
||||||
{
|
|
||||||
// Torrent already done, this can happen if the
|
|
||||||
// same file was sent multiple times.
|
|
||||||
if (torrent.progress === 1)
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
files : torrent.files,
|
|
||||||
numPeers : torrent.numPeers,
|
|
||||||
progress : 1,
|
|
||||||
active : false,
|
|
||||||
timeout : false
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onProgress = () =>
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
numPeers : torrent.numPeers,
|
|
||||||
progress : torrent.progress
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onProgress();
|
|
||||||
|
|
||||||
setInterval(onProgress, 500);
|
|
||||||
|
|
||||||
torrent.on('done', () =>
|
|
||||||
{
|
|
||||||
onProgress();
|
|
||||||
clearInterval(onProgress);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
files : torrent.files,
|
|
||||||
active : false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDownload = () =>
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
active : true
|
|
||||||
});
|
|
||||||
|
|
||||||
const magnetURI = this.props.data.file.magnet;
|
|
||||||
|
|
||||||
const existingTorrent = client.get(magnetURI);
|
|
||||||
|
|
||||||
if (existingTorrent)
|
|
||||||
{
|
|
||||||
// Never add duplicate torrents, use the existing one instead.
|
|
||||||
return this.handleTorrent(existingTorrent);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.add(magnetURI, this.handleTorrent);
|
|
||||||
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
if (this.state.active && this.state.numPeers === 0)
|
|
||||||
{
|
|
||||||
this.setState({
|
|
||||||
timeout : true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 10 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<div className='file-entry'>
|
|
||||||
<img className='file-avatar' src={this.props.data.picture || DEFAULT_PICTURE} />
|
|
||||||
|
|
||||||
<div className='file-content'>
|
|
||||||
{this.props.data.me ? (
|
|
||||||
<p>You shared a file.</p>
|
|
||||||
) : (
|
|
||||||
<p>{this.props.data.name} shared a file.</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!this.state.active && !this.state.files && (
|
|
||||||
<div className='file-info'>
|
|
||||||
{WebTorrent.WEBRTC_SUPPORT ? (
|
|
||||||
<span className='button' onClick={this.handleDownload}>
|
|
||||||
<img src='resources/images/download-icon.svg' />
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Your browser does not support downloading files using WebTorrent.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p>{magnet.decode(this.props.data.file.magnet).dn}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.active && this.state.numPeers === 0 && (
|
|
||||||
<Fragment>
|
|
||||||
<p>
|
|
||||||
Locating peers
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{this.state.timeout && (
|
|
||||||
<p>
|
|
||||||
If this process takes a long time, there might not be anyone seeding
|
|
||||||
this torrent. Try asking someone to reupload the file that you want.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.active && this.state.numPeers > 0 && (
|
|
||||||
<progress value={this.state.progress} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.files && (
|
|
||||||
<Fragment>
|
|
||||||
<p>Torrent finished downloading.</p>
|
|
||||||
|
|
||||||
{this.state.files.map((file, i) => (
|
|
||||||
<div className='file-info' key={i}>
|
|
||||||
<span className='button' onClick={() => this.saveFile(file)}>
|
|
||||||
<img src='resources/images/save-icon.svg' />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<p>{file.name}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FileEntryProps = {
|
|
||||||
data : PropTypes.shape({
|
|
||||||
name : PropTypes.string.isRequired,
|
|
||||||
picture : PropTypes.string,
|
|
||||||
file : PropTypes.shape({
|
|
||||||
magnet : PropTypes.string.isRequired
|
|
||||||
}).isRequired,
|
|
||||||
me : PropTypes.bool
|
|
||||||
}).isRequired,
|
|
||||||
notify : PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
FileEntry.propTypes = FileEntryProps;
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
notify : requestActions.notify
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
undefined,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(FileEntry);
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { compose } from 'redux';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import FileEntry, { FileEntryProps } from './FileEntry';
|
|
||||||
import scrollToBottom from '../Chat/scrollToBottom';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component cannot be pure, as we need to use
|
|
||||||
* refs to scroll to the bottom when new files arrive.
|
|
||||||
*/
|
|
||||||
class SharedFilesList extends Component
|
|
||||||
{
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
const { sharing } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='shared-files'>
|
|
||||||
{ sharing.length > 0 ?
|
|
||||||
sharing.map((entry, i) => (
|
|
||||||
<FileEntry
|
|
||||||
data={entry}
|
|
||||||
key={i}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
:<div className='empty'>
|
|
||||||
<p>No one has shared files yet...</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedFilesList.propTypes = {
|
|
||||||
sharing : PropTypes.arrayOf(FileEntryProps.data).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
|
||||||
({
|
|
||||||
sharing : state.sharing,
|
|
||||||
|
|
||||||
// Included to scroll to the bottom when the user
|
|
||||||
// actually opens the tab. When the component first
|
|
||||||
// mounts, the component is not visible and so the
|
|
||||||
// component has no height which can be used for scrolling.
|
|
||||||
tabOpen : state.toolarea.currentToolTab === 'files'
|
|
||||||
});
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
connect(mapStateToProps),
|
|
||||||
scrollToBottom()
|
|
||||||
)(SharedFilesList);
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import WebTorrent from 'webtorrent';
|
|
||||||
import createTorrent from 'create-torrent';
|
|
||||||
import randomString from 'random-string';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import * as stateActions from '../../redux/stateActions';
|
|
||||||
import * as requestActions from '../../redux/requestActions';
|
|
||||||
import { store } from '../../store';
|
|
||||||
import config from '../../../config';
|
|
||||||
import SharedFilesList from './SharedFilesList';
|
|
||||||
|
|
||||||
export const client = WebTorrent.WEBRTC_SUPPORT && new WebTorrent({
|
|
||||||
tracker : {
|
|
||||||
rtcConfig : {
|
|
||||||
iceServers : config.turnServers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const notifyPeers = (file) =>
|
|
||||||
{
|
|
||||||
const { displayName, picture } = store.getState().me;
|
|
||||||
|
|
||||||
store.dispatch(requestActions.sendFile(file, displayName, picture));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const shareFiles = async (files) =>
|
|
||||||
{
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
id : randomString({ length: 6 }).toLowerCase(),
|
|
||||||
text : 'Creating torrent',
|
|
||||||
type : 'info'
|
|
||||||
};
|
|
||||||
|
|
||||||
store.dispatch(stateActions.addNotification(notification));
|
|
||||||
|
|
||||||
createTorrent(files, (err, torrent) =>
|
|
||||||
{
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
return store.dispatch(requestActions.notify({
|
|
||||||
text : 'An error occured while uploading a file'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingTorrent = client.get(torrent);
|
|
||||||
|
|
||||||
if (existingTorrent)
|
|
||||||
{
|
|
||||||
return notifyPeers({
|
|
||||||
magnet : existingTorrent.magnetURI
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
client.seed(files, (newTorrent) =>
|
|
||||||
{
|
|
||||||
store.dispatch(stateActions.removeNotification(notification.id));
|
|
||||||
|
|
||||||
store.dispatch(requestActions.notify({
|
|
||||||
text : 'Torrent successfully created'
|
|
||||||
}));
|
|
||||||
|
|
||||||
notifyPeers({
|
|
||||||
magnet : newTorrent.magnetURI
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
class FileSharing extends Component
|
|
||||||
{
|
|
||||||
constructor(props)
|
|
||||||
{
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.fileInput = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileChange = async (event) =>
|
|
||||||
{
|
|
||||||
if (event.target.files.length > 0)
|
|
||||||
{
|
|
||||||
await shareFiles(event.target.files);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () =>
|
|
||||||
{
|
|
||||||
if (WebTorrent.WEBRTC_SUPPORT)
|
|
||||||
{
|
|
||||||
// We want to open the file dialog when we click a button
|
|
||||||
// instead of actually rendering the input element itself.
|
|
||||||
this.fileInput.current.click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
const buttonDescription = WebTorrent.WEBRTC_SUPPORT ?
|
|
||||||
'Share file' : 'File sharing not supported';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-component='FileSharing'>
|
|
||||||
<div className='sharing-toolbar'>
|
|
||||||
<input
|
|
||||||
style={{ display: 'none' }}
|
|
||||||
ref={this.fileInput}
|
|
||||||
type='file'
|
|
||||||
onChange={this.handleFileChange}
|
|
||||||
multiple
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
type='button'
|
|
||||||
onClick={this.handleClick}
|
|
||||||
className={classNames('share-file', {
|
|
||||||
disabled : !WebTorrent.WEBRTC_SUPPORT
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span>{buttonDescription}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SharedFilesList />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FileSharing;
|
|
||||||
|
|
@ -4,9 +4,9 @@ import ResizeObserver from 'resize-observer-polyfill';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as requestActions from '../redux/requestActions';
|
import { withRoomContext } from '../../RoomContext';
|
||||||
import Peer from './Peer';
|
import Peer from '../Containers/Peer';
|
||||||
import HiddenPeers from './HiddenPeers';
|
import HiddenPeers from '../Containers/HiddenPeers';
|
||||||
|
|
||||||
class Filmstrip extends Component
|
class Filmstrip extends Component
|
||||||
{
|
{
|
||||||
|
|
@ -114,14 +114,20 @@ class Filmstrip extends Component
|
||||||
|
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const { peers, advancedMode, spotlights, spotlightsLength } = this.props;
|
const {
|
||||||
|
roomClient,
|
||||||
|
peers,
|
||||||
|
advancedMode,
|
||||||
|
spotlights,
|
||||||
|
spotlightsLength
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const activePeerName = this.getActivePeerName();
|
const activePeerName = this.getActivePeerName();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='Filmstrip'>
|
<div data-component='Filmstrip'>
|
||||||
<div className='active-peer-container' ref={this.activePeerContainer}>
|
<div className='active-peer-container' ref={this.activePeerContainer}>
|
||||||
{peers[activePeerName] && (
|
<If condition={peers[activePeerName]}>
|
||||||
<div
|
<div
|
||||||
className='active-peer'
|
className='active-peer'
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -134,19 +140,19 @@ class Filmstrip extends Component
|
||||||
name={activePeerName}
|
name={activePeerName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</If>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='filmstrip'>
|
<div className='filmstrip'>
|
||||||
<div className='filmstrip-content'>
|
<div className='filmstrip-content'>
|
||||||
|
{ Object.keys(peers).map((peerName) =>
|
||||||
{
|
{
|
||||||
Object.keys(peers).map((peerName) =>
|
if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
spotlights.find((spotlightsElement) => spotlightsElement === peerName)?
|
|
||||||
<div
|
<div
|
||||||
key={peerName}
|
key={peerName}
|
||||||
onClick={() => this.props.setSelectedPeer(peerName)}
|
onClick={() => roomClient.setSelectedPeer(peerName)}
|
||||||
className={classnames('film', {
|
className={classnames('film', {
|
||||||
selected : this.props.selectedPeerName === peerName,
|
selected : this.props.selectedPeerName === peerName,
|
||||||
active : this.state.lastSpeaker === peerName
|
active : this.state.lastSpeaker === peerName
|
||||||
|
|
@ -159,18 +165,17 @@ class Filmstrip extends Component
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:null
|
|
||||||
);
|
);
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='hidden-peer-container'>
|
<div className='hidden-peer-container'>
|
||||||
{ (spotlightsLength<Object.keys(peers).length)?
|
<If condition={(spotlightsLength<Object.keys(peers).length)}>
|
||||||
<HiddenPeers
|
<HiddenPeers
|
||||||
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
hiddenPeersCount={Object.keys(peers).length-spotlightsLength}
|
||||||
/>:null
|
/>
|
||||||
}
|
</If>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -179,13 +184,13 @@ class Filmstrip extends Component
|
||||||
}
|
}
|
||||||
|
|
||||||
Filmstrip.propTypes = {
|
Filmstrip.propTypes = {
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
activeSpeakerName : PropTypes.string,
|
activeSpeakerName : PropTypes.string,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peers : PropTypes.object.isRequired,
|
peers : PropTypes.object.isRequired,
|
||||||
consumers : PropTypes.object.isRequired,
|
consumers : PropTypes.object.isRequired,
|
||||||
myName : PropTypes.string.isRequired,
|
myName : PropTypes.string.isRequired,
|
||||||
selectedPeerName : PropTypes.string,
|
selectedPeerName : PropTypes.string,
|
||||||
setSelectedPeer : PropTypes.func.isRequired,
|
|
||||||
spotlightsLength : PropTypes.number,
|
spotlightsLength : PropTypes.number,
|
||||||
spotlights : PropTypes.array.isRequired
|
spotlights : PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|
@ -205,11 +210,7 @@ const mapStateToProps = (state) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
export default withRoomContext(connect(
|
||||||
setSelectedPeer : requestActions.setSelectedPeer
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
undefined
|
||||||
)(Filmstrip);
|
)(Filmstrip));
|
||||||
|
|
@ -3,10 +3,9 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import { Appear } from '../transitions';
|
||||||
import { Appear } from './transitions';
|
import Peer from '../Containers/Peer';
|
||||||
import Peer from './Peer';
|
import HiddenPeers from '../Containers/HiddenPeers';
|
||||||
import HiddenPeers from './HiddenPeers';
|
|
||||||
import ResizeObserver from 'resize-observer-polyfill';
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
|
||||||
const RATIO = 1.334;
|
const RATIO = 1.334;
|
||||||
|
|
@ -105,38 +104,36 @@ class Peers extends React.Component
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='Peers' ref={this.peersRef}>
|
<div data-component='Peers' ref={this.peersRef}>
|
||||||
|
{ Object.keys(peers).map((peerName) =>
|
||||||
{
|
{
|
||||||
peers.map((peer) =>
|
if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
(spotlights.find(function(spotlightsElement)
|
<Appear key={peerName} duration={1000}>
|
||||||
{ return spotlightsElement == peer.name; }))?
|
|
||||||
<Appear key={peer.name} duration={1000}>
|
|
||||||
<div
|
<div
|
||||||
className={classnames('peer-container', {
|
className={classnames('peer-container', {
|
||||||
'selected' : this.props.selectedPeerName === peer.name,
|
'selected' : this.props.selectedPeerName === peerName,
|
||||||
'active-speaker' : peer.name === activeSpeakerName
|
'active-speaker' : peerName === activeSpeakerName
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className='peer-content'>
|
<div className='peer-content'>
|
||||||
<Peer
|
<Peer
|
||||||
advancedMode={advancedMode}
|
advancedMode={advancedMode}
|
||||||
name={peer.name}
|
name={peerName}
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Appear>
|
</Appear>
|
||||||
:null
|
|
||||||
);
|
);
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})}
|
||||||
<div className='hidden-peer-container'>
|
<div className='hidden-peer-container'>
|
||||||
{ (spotlightsLength<peers.length)?
|
<If condition={spotlightsLength < Object.keys(peers).length}>
|
||||||
<HiddenPeers
|
<HiddenPeers
|
||||||
hiddenPeersCount={peers.length-spotlightsLength}
|
hiddenPeersCount={Object.keys(peers).length - spotlightsLength}
|
||||||
/>:null
|
/>
|
||||||
}
|
</If>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -146,7 +143,7 @@ class Peers extends React.Component
|
||||||
Peers.propTypes =
|
Peers.propTypes =
|
||||||
{
|
{
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
|
peers : PropTypes.object.isRequired,
|
||||||
boxes : PropTypes.number,
|
boxes : PropTypes.number,
|
||||||
activeSpeakerName : PropTypes.string,
|
activeSpeakerName : PropTypes.string,
|
||||||
selectedPeerName : PropTypes.string,
|
selectedPeerName : PropTypes.string,
|
||||||
|
|
@ -156,14 +153,13 @@ Peers.propTypes =
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
const peers = Object.values(state.peers);
|
|
||||||
const spotlights = state.room.spotlights;
|
const spotlights = state.room.spotlights;
|
||||||
const spotlightsLength = spotlights ? state.room.spotlights.length : 0;
|
const spotlightsLength = spotlights ? state.room.spotlights.length : 0;
|
||||||
const boxes = spotlightsLength + Object.values(state.consumers)
|
const boxes = spotlightsLength + Object.values(state.consumers)
|
||||||
.filter((consumer) => consumer.source === 'screen').length;
|
.filter((consumer) => consumer.source === 'screen').length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peers,
|
peers : state.peers,
|
||||||
boxes,
|
boxes,
|
||||||
activeSpeakerName : state.room.activeSpeakerName,
|
activeSpeakerName : state.room.activeSpeakerName,
|
||||||
selectedPeerName : state.room.selectedPeerName,
|
selectedPeerName : state.room.selectedPeerName,
|
||||||
|
|
@ -9,21 +9,17 @@ import * as appPropTypes from './appPropTypes';
|
||||||
import * as requestActions from '../redux/requestActions';
|
import * as requestActions from '../redux/requestActions';
|
||||||
import * as stateActions from '../redux/stateActions';
|
import * as stateActions from '../redux/stateActions';
|
||||||
import { Appear } from './transitions';
|
import { Appear } from './transitions';
|
||||||
import Me from './Me';
|
import Me from './Containers/Me';
|
||||||
import Peers from './Peers';
|
import Peers from './Layouts/Peers';
|
||||||
import AudioPeers from './PeerAudio/AudioPeers';
|
import AudioPeers from './PeerAudio/AudioPeers';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
// import ToolAreaButton from './ToolArea/ToolAreaButton';
|
|
||||||
import ToolArea from './ToolArea/ToolArea';
|
import ToolArea from './ToolArea/ToolArea';
|
||||||
import FullScreenView from './FullScreenView';
|
import FullScreenView from './VideoContainers/FullScreenView';
|
||||||
import VideoWindow from './VideoWindow/VideoWindow';
|
import VideoWindow from './VideoWindow/VideoWindow';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
import { idle } from '../utils';
|
import { idle } from '../utils';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Controls/Sidebar';
|
||||||
import Filmstrip from './Filmstrip';
|
import Filmstrip from './Layouts/Filmstrip';
|
||||||
import { configureDragDrop, HoldingOverlay } from './FileSharing/DragDropSharing';
|
|
||||||
|
|
||||||
configureDragDrop();
|
|
||||||
|
|
||||||
// Hide toolbars after 10 seconds of inactivity.
|
// Hide toolbars after 10 seconds of inactivity.
|
||||||
const TIMEOUT = 10 * 1000;
|
const TIMEOUT = 10 * 1000;
|
||||||
|
|
@ -79,8 +75,6 @@ class Room extends React.Component
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<HoldingOverlay />
|
|
||||||
|
|
||||||
<Appear duration={300}>
|
<Appear duration={300}>
|
||||||
<div data-component='Room'>
|
<div data-component='Room'>
|
||||||
<CookieConsent>
|
<CookieConsent>
|
||||||
|
|
@ -97,13 +91,12 @@ class Room extends React.Component
|
||||||
|
|
||||||
<Notifications />
|
<Notifications />
|
||||||
|
|
||||||
{room.advancedMode ?
|
<If condition={room.advancedMode}>
|
||||||
<div className='state' data-tip='Server status'>
|
<div className='state' data-tip='Server status'>
|
||||||
<div className={classnames('icon', room.state)} />
|
<div className={classnames('icon', room.state)} />
|
||||||
<p className={classnames('text', room.state)}>{room.state}</p>
|
<p className={classnames('text', room.state)}>{room.state}</p>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames('room-link-wrapper room-controls', {
|
className={classnames('room-link-wrapper room-controls', {
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import * as appPropTypes from './appPropTypes';
|
|
||||||
import * as requestActions from '../redux/requestActions';
|
|
||||||
import * as stateActions from '../redux/stateActions';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Dropdown from 'react-dropdown';
|
|
||||||
import ReactTooltip from 'react-tooltip';
|
|
||||||
|
|
||||||
const modes = [ {
|
|
||||||
value : 'democratic',
|
|
||||||
label : 'Democratic view'
|
|
||||||
}, {
|
|
||||||
value : 'filmstrip',
|
|
||||||
label : 'Filmstrip view'
|
|
||||||
} ];
|
|
||||||
|
|
||||||
const findOption = (options, value) => options.find((option) => option.value === value);
|
|
||||||
|
|
||||||
const Settings = ({
|
|
||||||
room, me, onToggleAdvancedMode, handleChangeWebcam,
|
|
||||||
handleChangeAudioDevice, handleChangeMode
|
|
||||||
}) =>
|
|
||||||
{
|
|
||||||
let webcams;
|
|
||||||
|
|
||||||
if (me.webcamDevices)
|
|
||||||
webcams = Array.from(me.webcamDevices.values());
|
|
||||||
else
|
|
||||||
webcams = [];
|
|
||||||
|
|
||||||
let audioDevices;
|
|
||||||
let audioDevicesText;
|
|
||||||
|
|
||||||
if (me.canChangeAudioDevice)
|
|
||||||
audioDevicesText = 'Select audio input device';
|
|
||||||
else
|
|
||||||
audioDevicesText = 'Unable to select audio input device';
|
|
||||||
|
|
||||||
if (me.audioDevices)
|
|
||||||
audioDevices = Array.from(me.audioDevices.values());
|
|
||||||
else
|
|
||||||
audioDevices = [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-component='Settings'>
|
|
||||||
<div className='settings'>
|
|
||||||
<Dropdown
|
|
||||||
options={webcams}
|
|
||||||
value={findOption(webcams, me.selectedWebcam)}
|
|
||||||
onChange={(webcam) => handleChangeWebcam(webcam.value)}
|
|
||||||
placeholder={'Select camera'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dropdown
|
|
||||||
disabled={!me.canChangeAudioDevice}
|
|
||||||
options={audioDevices}
|
|
||||||
value={findOption(audioDevices, me.selectedAudioDevice)}
|
|
||||||
onChange={(device) => handleChangeAudioDevice(device.value)}
|
|
||||||
placeholder={audioDevicesText}
|
|
||||||
/>
|
|
||||||
<ReactTooltip
|
|
||||||
effect='solid'
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-tip='keyboard shortcut: ‘a‘'
|
|
||||||
data-type='dark'
|
|
||||||
data-place='left'
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id='room-mode'
|
|
||||||
type='checkbox'
|
|
||||||
checked={room.advancedMode}
|
|
||||||
onChange={onToggleAdvancedMode}
|
|
||||||
/>
|
|
||||||
<label htmlFor='room-mode'>Advanced mode</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-tip='keyboard shortcut: type a digit'
|
|
||||||
data-type='dark'
|
|
||||||
data-place='left'
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
options={modes}
|
|
||||||
value={findOption(modes, room.mode)}
|
|
||||||
onChange={(mode) => handleChangeMode(mode.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Settings.propTypes =
|
|
||||||
{
|
|
||||||
me : appPropTypes.Me.isRequired,
|
|
||||||
room : appPropTypes.Room.isRequired,
|
|
||||||
handleChangeWebcam : PropTypes.func.isRequired,
|
|
||||||
handleChangeAudioDevice : PropTypes.func.isRequired,
|
|
||||||
onToggleAdvancedMode : PropTypes.func.isRequired,
|
|
||||||
handleChangeMode : PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
me : state.me,
|
|
||||||
room : state.room
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
handleChangeWebcam : requestActions.changeWebcam,
|
|
||||||
handleChangeAudioDevice : requestActions.changeAudioDevice,
|
|
||||||
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
|
|
||||||
handleChangeMode : stateActions.setDisplayMode
|
|
||||||
};
|
|
||||||
|
|
||||||
const SettingsContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Settings);
|
|
||||||
|
|
||||||
export default SettingsContainer;
|
|
||||||
|
|
@ -1,17 +1,28 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as stateActions from '../../redux/stateActions';
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
import * as requestActions from '../../redux/requestActions';
|
|
||||||
import MessageList from './MessageList';
|
import MessageList from './MessageList';
|
||||||
|
|
||||||
class Chat extends Component
|
class Chat extends Component
|
||||||
{
|
{
|
||||||
|
createNewMessage(text, sender, name, picture)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'message',
|
||||||
|
text,
|
||||||
|
time : Date.now(),
|
||||||
|
name,
|
||||||
|
sender,
|
||||||
|
picture
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
|
roomClient,
|
||||||
senderPlaceHolder,
|
senderPlaceHolder,
|
||||||
onSendMessage,
|
|
||||||
disabledInput,
|
disabledInput,
|
||||||
autofocus,
|
autofocus,
|
||||||
displayName,
|
displayName,
|
||||||
|
|
@ -23,7 +34,19 @@ class Chat extends Component
|
||||||
<MessageList />
|
<MessageList />
|
||||||
<form
|
<form
|
||||||
data-component='Sender'
|
data-component='Sender'
|
||||||
onSubmit={(e) => { onSendMessage(e, displayName, picture); }}
|
onSubmit={(e) =>
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
const userInput = e.target.message.value;
|
||||||
|
|
||||||
|
if (userInput)
|
||||||
|
{
|
||||||
|
const message = this.createNewMessage(userInput, 'response', displayName, picture);
|
||||||
|
|
||||||
|
roomClient.sendChatMessage(message);
|
||||||
|
}
|
||||||
|
e.target.message.value = '';
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
|
|
@ -47,8 +70,8 @@ class Chat extends Component
|
||||||
|
|
||||||
Chat.propTypes =
|
Chat.propTypes =
|
||||||
{
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
senderPlaceHolder : PropTypes.string,
|
senderPlaceHolder : PropTypes.string,
|
||||||
onSendMessage : PropTypes.func,
|
|
||||||
disabledInput : PropTypes.bool,
|
disabledInput : PropTypes.bool,
|
||||||
autofocus : PropTypes.bool,
|
autofocus : PropTypes.bool,
|
||||||
displayName : PropTypes.string,
|
displayName : PropTypes.string,
|
||||||
|
|
@ -71,27 +94,8 @@ const mapStateToProps = (state) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const ChatContainer = withRoomContext(connect(
|
||||||
{
|
mapStateToProps
|
||||||
return {
|
)(Chat));
|
||||||
onSendMessage : (event, displayName, picture) =>
|
|
||||||
{
|
|
||||||
event.preventDefault();
|
|
||||||
const userInput = event.target.message.value;
|
|
||||||
|
|
||||||
if (userInput)
|
|
||||||
{
|
|
||||||
dispatch(stateActions.addUserMessage(userInput));
|
|
||||||
dispatch(requestActions.sendChatMessage(userInput, displayName, picture));
|
|
||||||
}
|
|
||||||
event.target.message.value = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ChatContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Chat);
|
|
||||||
|
|
||||||
export default ChatContainer;
|
export default ChatContainer;
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import marked from 'marked';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import scrollToBottom from '../scrollToBottom';
|
||||||
|
|
||||||
|
const linkRenderer = new marked.Renderer();
|
||||||
|
|
||||||
|
linkRenderer.link = (href, title, text) =>
|
||||||
|
{
|
||||||
|
title = title ? title : href;
|
||||||
|
text = text ? text : href;
|
||||||
|
|
||||||
|
return (`<a target='_blank' href='${ href }' title='${ title }'>${ text }</a>`);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageList extends Component
|
||||||
|
{
|
||||||
|
getTimeString(time)
|
||||||
|
{
|
||||||
|
return `${(time.getHours() < 10 ? '0' : '')}${time.getHours()}:${(time.getMinutes() < 10 ? '0' : '')}${time.getMinutes()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
const {
|
||||||
|
chatmessages
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-component='MessageList' id='messages'>
|
||||||
|
<Choose>
|
||||||
|
<When condition={chatmessages.length > 0}>
|
||||||
|
{
|
||||||
|
chatmessages.map((message, i) =>
|
||||||
|
{
|
||||||
|
const messageTime = new Date(message.time);
|
||||||
|
|
||||||
|
const picture = (message.sender === 'response' ?
|
||||||
|
message.picture : this.props.myPicture) || 'resources/images/avatar-empty.jpeg';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='message' key={i}>
|
||||||
|
<div className={message.sender}>
|
||||||
|
<img className='message-avatar' src={picture} />
|
||||||
|
|
||||||
|
<div className='message-content'>
|
||||||
|
<div
|
||||||
|
className='message-text'
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={{ __html : marked.parse(
|
||||||
|
message.text,
|
||||||
|
{ sanitize: true, renderer: linkRenderer }
|
||||||
|
) }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className='message-time'>
|
||||||
|
{message.name} - {this.getTimeString(messageTime)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</When>
|
||||||
|
<Otherwise>
|
||||||
|
<div className='empty'>
|
||||||
|
<p>No one has said anything yet...</p>
|
||||||
|
</div>
|
||||||
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageList.propTypes =
|
||||||
|
{
|
||||||
|
chatmessages : PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
myPicture : PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
chatmessages : state.chatmessages,
|
||||||
|
myPicture : state.me.picture
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageListContainer = compose(
|
||||||
|
connect(mapStateToProps),
|
||||||
|
scrollToBottom()
|
||||||
|
)(MessageList);
|
||||||
|
|
||||||
|
export default MessageListContainer;
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
|
import magnet from 'magnet-uri';
|
||||||
|
|
||||||
|
const DEFAULT_PICTURE = 'resources/images/avatar-empty.jpeg';
|
||||||
|
|
||||||
|
class File extends Component
|
||||||
|
{
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
const {
|
||||||
|
roomClient,
|
||||||
|
torrentSupport,
|
||||||
|
file
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='file-entry'>
|
||||||
|
<img className='file-avatar' src={file.picture || DEFAULT_PICTURE} />
|
||||||
|
|
||||||
|
<div className='file-content'>
|
||||||
|
<Choose>
|
||||||
|
<When condition={file.me}>
|
||||||
|
<p>You shared a file.</p>
|
||||||
|
</When>
|
||||||
|
<Otherwise>
|
||||||
|
<p>{file.displayName} shared a file.</p>
|
||||||
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
|
||||||
|
<If condition={!file.active && !file.files}>
|
||||||
|
<div className='file-info'>
|
||||||
|
<Choose>
|
||||||
|
<When condition={torrentSupport}>
|
||||||
|
<span
|
||||||
|
className='button'
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
roomClient.handleDownload(file.magnetUri);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src='resources/images/download-icon.svg' />
|
||||||
|
</span>
|
||||||
|
</When>
|
||||||
|
<Otherwise>
|
||||||
|
<p>
|
||||||
|
Your browser does not support downloading files using WebTorrent.
|
||||||
|
</p>
|
||||||
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
<p>{magnet.decode(file.magnetUri).dn}</p>
|
||||||
|
</div>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<If condition={file.timeout}>
|
||||||
|
<Fragment>
|
||||||
|
<p>
|
||||||
|
If this process takes a long time, there might not be anyone seeding
|
||||||
|
this torrent. Try asking someone to reupload the file that you want.
|
||||||
|
</p>
|
||||||
|
</Fragment>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<If condition={file.active}>
|
||||||
|
<progress value={file.progress} />
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<If condition={file.files}>
|
||||||
|
<Fragment>
|
||||||
|
<p>File finished downloading.</p>
|
||||||
|
|
||||||
|
{file.files.map((sharedFile, i) => (
|
||||||
|
<div className='file-info' key={i}>
|
||||||
|
<span
|
||||||
|
className='button'
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
roomClient.saveFile(sharedFile);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src='resources/images/save-icon.svg' />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p>{sharedFile.name}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File.propTypes = {
|
||||||
|
roomClient : PropTypes.object.isRequired,
|
||||||
|
torrentSupport : PropTypes.bool.isRequired,
|
||||||
|
file : PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { magnetUri }) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
file : state.files[magnetUri],
|
||||||
|
torrentSupport : state.room.torrentSupport
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRoomContext(connect(
|
||||||
|
mapStateToProps
|
||||||
|
)(File));
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import scrollToBottom from '../scrollToBottom';
|
||||||
|
import File from './File';
|
||||||
|
|
||||||
|
class FileList extends Component
|
||||||
|
{
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
const {
|
||||||
|
files
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='shared-files'>
|
||||||
|
{ Object.keys(files).map((magnetUri) =>
|
||||||
|
<File key={magnetUri} magnetUri={magnetUri} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileList.propTypes = {
|
||||||
|
files : PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
files : state.files
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
connect(mapStateToProps),
|
||||||
|
scrollToBottom()
|
||||||
|
)(FileList);
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
|
import FileList from './FileList';
|
||||||
|
|
||||||
|
class FileSharing extends Component
|
||||||
|
{
|
||||||
|
constructor(props)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._fileInput = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileChange = async (event) =>
|
||||||
|
{
|
||||||
|
if (event.target.files.length > 0)
|
||||||
|
{
|
||||||
|
this.props.roomClient.shareFiles(event.target.files);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClick = () =>
|
||||||
|
{
|
||||||
|
if (this.props.torrentSupport)
|
||||||
|
{
|
||||||
|
// We want to open the file dialog when we click a button
|
||||||
|
// instead of actually rendering the input element itself.
|
||||||
|
this._fileInput.current.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
const {
|
||||||
|
torrentSupport
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const buttonDescription = torrentSupport ?
|
||||||
|
'Share file' : 'File sharing not supported';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-component='FileSharing'>
|
||||||
|
<div className='sharing-toolbar'>
|
||||||
|
<input
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
ref={this._fileInput}
|
||||||
|
type='file'
|
||||||
|
onChange={this.handleFileChange}
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
type='button'
|
||||||
|
onClick={this.handleClick}
|
||||||
|
className={classNames('share-file', {
|
||||||
|
disabled : !torrentSupport
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>{buttonDescription}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FileList />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSharing.propTypes = {
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
|
torrentSupport : PropTypes.bool.isRequired,
|
||||||
|
tabOpen : PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
torrentSupport : state.room.torrentSupport,
|
||||||
|
tabOpen : state.toolarea.currentToolTab === 'files'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRoomContext(connect(
|
||||||
|
mapStateToProps
|
||||||
|
)(FileSharing));
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Me } from '../appPropTypes';
|
import { Me } from '../../appPropTypes';
|
||||||
|
|
||||||
const ListMe = ({ me }) =>
|
const ListMe = ({ me }) =>
|
||||||
{
|
{
|
||||||
|
|
@ -2,19 +2,16 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../../appPropTypes';
|
||||||
import * as requestActions from '../../redux/requestActions';
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
|
|
||||||
const ListPeer = (props) =>
|
const ListPeer = (props) =>
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
|
roomClient,
|
||||||
peer,
|
peer,
|
||||||
micConsumer,
|
micConsumer,
|
||||||
screenConsumer,
|
screenConsumer
|
||||||
onMuteMic,
|
|
||||||
onUnmuteMic,
|
|
||||||
onDisableScreen,
|
|
||||||
onEnableScreen
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const micEnabled = (
|
const micEnabled = (
|
||||||
|
|
@ -39,7 +36,7 @@ const ListPeer = (props) =>
|
||||||
{peer.displayName}
|
{peer.displayName}
|
||||||
</div>
|
</div>
|
||||||
<div className='indicators'>
|
<div className='indicators'>
|
||||||
{peer.raiseHandState ?
|
<If condition={peer.raiseHandState}>
|
||||||
<div className={
|
<div className={
|
||||||
classnames(
|
classnames(
|
||||||
'icon', 'raise-hand', {
|
'icon', 'raise-hand', {
|
||||||
|
|
@ -49,14 +46,13 @@ const ListPeer = (props) =>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
<div className='volume-container'>
|
<div className='volume-container'>
|
||||||
<div className={classnames('bar', `level${micEnabled && micConsumer ? micConsumer.volume:0}`)} />
|
<div className={classnames('bar', `level${micEnabled && micConsumer ? micConsumer.volume:0}`)} />
|
||||||
</div>
|
</div>
|
||||||
<div className='controls'>
|
<div className='controls'>
|
||||||
{ screenConsumer ?
|
<If condition={screenConsumer}>
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'screen', {
|
className={classnames('button', 'screen', {
|
||||||
on : screenVisible,
|
on : screenVisible,
|
||||||
|
|
@ -67,11 +63,11 @@ const ListPeer = (props) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
screenVisible ?
|
screenVisible ?
|
||||||
onDisableScreen(peer.name) : onEnableScreen(peer.name);
|
roomClient.modifyPeerConsumer(peer.name, 'screen', true) :
|
||||||
|
roomClient.modifyPeerConsumer(peer.name, 'screen', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'mic', {
|
className={classnames('button', 'mic', {
|
||||||
on : micEnabled,
|
on : micEnabled,
|
||||||
|
|
@ -81,7 +77,9 @@ const ListPeer = (props) =>
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
micEnabled ? onMuteMic(peer.name) : onUnmuteMic(peer.name);
|
micEnabled ?
|
||||||
|
roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
|
||||||
|
roomClient.modifyPeerConsumer(peer.name, 'mic', false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,17 +89,12 @@ const ListPeer = (props) =>
|
||||||
|
|
||||||
ListPeer.propTypes =
|
ListPeer.propTypes =
|
||||||
{
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peer : appPropTypes.Peer.isRequired,
|
peer : appPropTypes.Peer.isRequired,
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer,
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
screenConsumer : appPropTypes.Consumer,
|
screenConsumer : appPropTypes.Consumer
|
||||||
onMuteMic : PropTypes.func.isRequired,
|
|
||||||
onUnmuteMic : PropTypes.func.isRequired,
|
|
||||||
onEnableWebcam : PropTypes.func.isRequired,
|
|
||||||
onDisableWebcam : PropTypes.func.isRequired,
|
|
||||||
onEnableScreen : PropTypes.func.isRequired,
|
|
||||||
onDisableScreen : PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, { name }) =>
|
const mapStateToProps = (state, { name }) =>
|
||||||
|
|
@ -124,40 +117,8 @@ const mapStateToProps = (state, { name }) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const ListPeerContainer = withRoomContext(connect(
|
||||||
{
|
mapStateToProps
|
||||||
return {
|
)(ListPeer));
|
||||||
onMuteMic : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.mutePeerAudio(peerName));
|
|
||||||
},
|
|
||||||
onUnmuteMic : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.unmutePeerAudio(peerName));
|
|
||||||
},
|
|
||||||
onEnableWebcam : (peerName) =>
|
|
||||||
{
|
|
||||||
|
|
||||||
dispatch(requestActions.resumePeerVideo(peerName));
|
|
||||||
},
|
|
||||||
onDisableWebcam : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.pausePeerVideo(peerName));
|
|
||||||
},
|
|
||||||
onEnableScreen : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.resumePeerScreen(peerName));
|
|
||||||
},
|
|
||||||
onDisableScreen : (peerName) =>
|
|
||||||
{
|
|
||||||
dispatch(requestActions.pausePeerScreen(peerName));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListPeerContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ListPeer);
|
|
||||||
|
|
||||||
export default ListPeerContainer;
|
export default ListPeerContainer;
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../../appPropTypes';
|
||||||
import * as requestActions from '../../redux/requestActions';
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ListPeer from './ListPeer';
|
import ListPeer from './ListPeer';
|
||||||
import ListMe from './ListMe';
|
import ListMe from './ListMe';
|
||||||
|
|
||||||
const ParticipantList =
|
const ParticipantList =
|
||||||
({
|
({
|
||||||
|
roomClient,
|
||||||
advancedMode,
|
advancedMode,
|
||||||
peers,
|
peers,
|
||||||
setSelectedPeer,
|
|
||||||
selectedPeerName,
|
selectedPeerName,
|
||||||
spotlights
|
spotlights
|
||||||
}) => (
|
}) => (
|
||||||
|
|
@ -33,7 +33,7 @@ const ParticipantList =
|
||||||
className={classNames('list-item', {
|
className={classNames('list-item', {
|
||||||
selected : peer.name === selectedPeerName
|
selected : peer.name === selectedPeerName
|
||||||
})}
|
})}
|
||||||
onClick={() => setSelectedPeer(peer.name)}
|
onClick={() => roomClient.setSelectedPeer(peer.name)}
|
||||||
>
|
>
|
||||||
<ListPeer name={peer.name} advancedMode={advancedMode} />
|
<ListPeer name={peer.name} advancedMode={advancedMode} />
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -52,7 +52,7 @@ const ParticipantList =
|
||||||
className={classNames('list-item', {
|
className={classNames('list-item', {
|
||||||
selected : peer.name === selectedPeerName
|
selected : peer.name === selectedPeerName
|
||||||
})}
|
})}
|
||||||
onClick={() => setSelectedPeer(peer.name)}
|
onClick={() => roomClient.setSelectedPeer(peer.name)}
|
||||||
>
|
>
|
||||||
<ListPeer name={peer.name} advancedMode={advancedMode} />
|
<ListPeer name={peer.name} advancedMode={advancedMode} />
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -64,9 +64,9 @@ const ParticipantList =
|
||||||
|
|
||||||
ParticipantList.propTypes =
|
ParticipantList.propTypes =
|
||||||
{
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
advancedMode : PropTypes.bool,
|
advancedMode : PropTypes.bool,
|
||||||
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
|
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
|
||||||
setSelectedPeer : PropTypes.func.isRequired,
|
|
||||||
selectedPeerName : PropTypes.string,
|
selectedPeerName : PropTypes.string,
|
||||||
spotlights : PropTypes.array.isRequired
|
spotlights : PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|
@ -82,13 +82,8 @@ const mapStateToProps = (state) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const ParticipantListContainer = withRoomContext(connect(
|
||||||
setSelectedPeer : requestActions.setSelectedPeer
|
mapStateToProps
|
||||||
};
|
)(ParticipantList));
|
||||||
|
|
||||||
const ParticipantListContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ParticipantList);
|
|
||||||
|
|
||||||
export default ParticipantListContainer;
|
export default ParticipantListContainer;
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as appPropTypes from '../../appPropTypes';
|
||||||
|
import { withRoomContext } from '../../../RoomContext';
|
||||||
|
import * as stateActions from '../../../redux/stateActions';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Dropdown from 'react-dropdown';
|
||||||
|
import ReactTooltip from 'react-tooltip';
|
||||||
|
|
||||||
|
const modes = [ {
|
||||||
|
value : 'democratic',
|
||||||
|
label : 'Democratic view'
|
||||||
|
}, {
|
||||||
|
value : 'filmstrip',
|
||||||
|
label : 'Filmstrip view'
|
||||||
|
} ];
|
||||||
|
|
||||||
|
const findOption = (options, value) => options.find((option) => option.value === value);
|
||||||
|
|
||||||
|
const Settings = ({
|
||||||
|
roomClient,
|
||||||
|
room,
|
||||||
|
me,
|
||||||
|
onToggleAdvancedMode,
|
||||||
|
handleChangeMode
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
let webcams;
|
||||||
|
|
||||||
|
if (me.webcamDevices)
|
||||||
|
webcams = Array.from(me.webcamDevices.values());
|
||||||
|
else
|
||||||
|
webcams = [];
|
||||||
|
|
||||||
|
let audioDevices;
|
||||||
|
let audioDevicesText;
|
||||||
|
|
||||||
|
if (me.canChangeAudioDevice)
|
||||||
|
audioDevicesText = 'Select audio input device';
|
||||||
|
else
|
||||||
|
audioDevicesText = 'Unable to select audio input device';
|
||||||
|
|
||||||
|
if (me.audioDevices)
|
||||||
|
audioDevices = Array.from(me.audioDevices.values());
|
||||||
|
else
|
||||||
|
audioDevices = [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='settings'>
|
||||||
|
<Dropdown
|
||||||
|
options={webcams}
|
||||||
|
value={findOption(webcams, me.selectedWebcam)}
|
||||||
|
onChange={(webcam) => roomClient.changeWebcam(webcam.value)}
|
||||||
|
placeholder={'Select camera'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
disabled={!me.canChangeAudioDevice}
|
||||||
|
options={audioDevices}
|
||||||
|
value={findOption(audioDevices, me.selectedAudioDevice)}
|
||||||
|
onChange={(device) => roomClient.changeAudioDevice(device.value)}
|
||||||
|
placeholder={audioDevicesText}
|
||||||
|
/>
|
||||||
|
<ReactTooltip
|
||||||
|
effect='solid'
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
data-tip='keyboard shortcut: ‘a‘'
|
||||||
|
data-type='dark'
|
||||||
|
data-place='left'
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id='room-mode'
|
||||||
|
type='checkbox'
|
||||||
|
checked={room.advancedMode}
|
||||||
|
onChange={onToggleAdvancedMode}
|
||||||
|
/>
|
||||||
|
<label htmlFor='room-mode'>Advanced mode</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-tip='keyboard shortcut: type a digit'
|
||||||
|
data-type='dark'
|
||||||
|
data-place='left'
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
options={modes}
|
||||||
|
value={findOption(modes, room.mode)}
|
||||||
|
onChange={(mode) => handleChangeMode(mode.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Settings.propTypes =
|
||||||
|
{
|
||||||
|
roomClient : PropTypes.any.isRequired,
|
||||||
|
me : appPropTypes.Me.isRequired,
|
||||||
|
room : appPropTypes.Room.isRequired,
|
||||||
|
onToggleAdvancedMode : PropTypes.func.isRequired,
|
||||||
|
handleChangeMode : PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
me : state.me,
|
||||||
|
room : state.room
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
|
||||||
|
handleChangeMode : stateActions.setDisplayMode
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsContainer = withRoomContext(connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Settings));
|
||||||
|
|
||||||
|
export default SettingsContainer;
|
||||||
|
|
@ -3,10 +3,10 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as stateActions from '../../redux/stateActions';
|
import * as stateActions from '../../redux/stateActions';
|
||||||
import ParticipantList from '../ParticipantList/ParticipantList';
|
import ParticipantList from './ParticipantList/ParticipantList';
|
||||||
import Chat from '../Chat/Chat';
|
import Chat from './Chat/Chat';
|
||||||
import Settings from '../Settings';
|
import Settings from './Settings/Settings';
|
||||||
import FileSharing from '../FileSharing';
|
import FileSharing from './FileSharing/FileSharing';
|
||||||
import TabHeader from './TabHeader';
|
import TabHeader from './TabHeader';
|
||||||
|
|
||||||
class ToolArea extends React.Component
|
class ToolArea extends React.Component
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import * as stateActions from '../../redux/stateActions';
|
|
||||||
|
|
||||||
class ToolAreaButton extends React.Component
|
|
||||||
{
|
|
||||||
render()
|
|
||||||
{
|
|
||||||
const {
|
|
||||||
toolAreaOpen,
|
|
||||||
toggleToolArea,
|
|
||||||
unread,
|
|
||||||
visible
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-component='ToolAreaButton'
|
|
||||||
className={classnames('room-controls', {
|
|
||||||
on : toolAreaOpen,
|
|
||||||
visible
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classnames('button toolarea-button', {
|
|
||||||
on : toolAreaOpen
|
|
||||||
})}
|
|
||||||
data-tip='Open tools'
|
|
||||||
data-type='dark'
|
|
||||||
onClick={() => toggleToolArea()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!toolAreaOpen && unread > 0 && (
|
|
||||||
<span className={classnames('badge', { long: unread >= 10 })}>
|
|
||||||
{unread}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolAreaButton.propTypes =
|
|
||||||
{
|
|
||||||
toolAreaOpen : PropTypes.bool.isRequired,
|
|
||||||
toggleToolArea : PropTypes.func.isRequired,
|
|
||||||
unread : PropTypes.number.isRequired,
|
|
||||||
visible : PropTypes.bool.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
toolAreaOpen : state.toolarea.toolAreaOpen,
|
|
||||||
visible : state.room.toolbarsVisible,
|
|
||||||
unread : state.toolarea.unreadMessages + state.toolarea.unreadFiles
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
toggleToolArea : () =>
|
|
||||||
{
|
|
||||||
dispatch(stateActions.toggleToolArea());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToolAreaButtonContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ToolAreaButton);
|
|
||||||
|
|
||||||
export default ToolAreaButtonContainer;
|
|
||||||
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as stateActions from '../redux/stateActions';
|
import * as stateActions from '../../redux/stateActions';
|
||||||
import FullView from './FullView';
|
import FullView from './FullView';
|
||||||
|
|
||||||
const FullScreenView = (props) =>
|
const FullScreenView = (props) =>
|
||||||
|
|
@ -31,12 +31,11 @@ const FullScreenView = (props) =>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='FullScreenView'>
|
<div data-component='FullScreenView'>
|
||||||
{consumerVisible && !consumer.supported ?
|
<If condition={consumerVisible && !consumer.supported}>
|
||||||
<div className='incompatible-video'>
|
<div className='incompatible-video'>
|
||||||
<p>incompatible video</p>
|
<p>incompatible video</p>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
<div className='controls'>
|
<div className='controls'>
|
||||||
<div
|
<div
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Spinner from 'react-spinner';
|
|
||||||
|
|
||||||
export default class FullView extends React.Component
|
export default class FullView extends React.Component
|
||||||
{
|
{
|
||||||
|
|
@ -34,13 +33,6 @@ export default class FullView extends React.Component
|
||||||
autoPlay
|
autoPlay
|
||||||
muted={Boolean(true)}
|
muted={Boolean(true)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{videoProfile === 'none' ?
|
|
||||||
<div className='spinner-container'>
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Spinner from 'react-spinner';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import EditableInput from '../Controls/EditableInput';
|
||||||
import EditableInput from './EditableInput';
|
|
||||||
|
|
||||||
export default class PeerView extends React.Component
|
export default class PeerView extends React.Component
|
||||||
{
|
{
|
||||||
|
|
@ -52,30 +51,27 @@ export default class PeerView extends React.Component
|
||||||
return (
|
return (
|
||||||
<div data-component='PeerView'>
|
<div data-component='PeerView'>
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
{advancedMode ?
|
<If condition={advancedMode}>
|
||||||
<div className={classnames('media', { 'is-me': isMe })}>
|
<div className={classnames('media', { 'is-me': isMe })}>
|
||||||
<div className='box'>
|
<div className='box'>
|
||||||
{audioCodec ?
|
<If condition={audioCodec}>
|
||||||
<p className='codec'>{audioCodec}</p>
|
<p className='codec'>{audioCodec}</p>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
{videoCodec ?
|
<If condition={videoCodec}>
|
||||||
<p className='codec'>{videoCodec} {videoProfile}</p>
|
<p className='codec'>{videoCodec} {videoProfile}</p>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
{(videoVisible && videoWidth !== null) ?
|
<If condition={(videoVisible && videoWidth !== null)}>
|
||||||
<p className='resolution'>{videoWidth}x{videoHeight}</p>
|
<p className='resolution'>{videoWidth}x{videoHeight}</p>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
<div className={classnames('peer', { 'is-me': isMe })}>
|
<div className={classnames('peer', { 'is-me': isMe })}>
|
||||||
{isMe ?
|
<Choose>
|
||||||
|
<When condition={isMe}>
|
||||||
<EditableInput
|
<EditableInput
|
||||||
value={peer.displayName}
|
value={peer.displayName}
|
||||||
propName='displayName'
|
propName='displayName'
|
||||||
|
|
@ -90,13 +86,15 @@ export default class PeerView extends React.Component
|
||||||
}}
|
}}
|
||||||
onChange={({ displayName }) => onChangeDisplayName(displayName)}
|
onChange={({ displayName }) => onChangeDisplayName(displayName)}
|
||||||
/>
|
/>
|
||||||
:
|
</When>
|
||||||
|
<Otherwise>
|
||||||
<span className='display-name'>
|
<span className='display-name'>
|
||||||
{peer.displayName}
|
{peer.displayName}
|
||||||
</span>
|
</span>
|
||||||
}
|
</Otherwise>
|
||||||
|
</Choose>
|
||||||
|
|
||||||
{advancedMode ?
|
<If condition={advancedMode}>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<span
|
<span
|
||||||
className={classnames('device-icon', peer.device.flag)}
|
className={classnames('device-icon', peer.device.flag)}
|
||||||
|
|
@ -105,8 +103,7 @@ export default class PeerView extends React.Component
|
||||||
{peer.device.name} {Math.floor(peer.device.version) || null}
|
{peer.device.name} {Math.floor(peer.device.version) || null}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -124,13 +121,6 @@ export default class PeerView extends React.Component
|
||||||
<div className='volume-container'>
|
<div className='volume-container'>
|
||||||
<div className={classnames('bar', `level${volume}`)} />
|
<div className={classnames('bar', `level${volume}`)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{videoProfile === 'none' ?
|
|
||||||
<div className='spinner-container'>
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Spinner from 'react-spinner';
|
|
||||||
|
|
||||||
export default class ScreenView extends React.Component
|
export default class ScreenView extends React.Component
|
||||||
{
|
{
|
||||||
|
|
@ -41,25 +40,21 @@ export default class ScreenView extends React.Component
|
||||||
return (
|
return (
|
||||||
<div data-component='ScreenView'>
|
<div data-component='ScreenView'>
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
{advancedMode ?
|
<If condition={advancedMode}>
|
||||||
<div className={classnames('media', { 'is-me': isMe })}>
|
<div className={classnames('media', { 'is-me': isMe })}>
|
||||||
{screenVisible ?
|
<If condition={screenVisible}>
|
||||||
<div className='box'>
|
<div className='box'>
|
||||||
{screenCodec ?
|
<If condition={screenCodec}>
|
||||||
<p className='codec'>{screenCodec} {screenProfile}</p>
|
<p className='codec'>{screenCodec} {screenProfile}</p>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
|
|
||||||
{(screenVisible && screenWidth !== null) ?
|
<If condition={(screenVisible && screenWidth !== null)}>
|
||||||
<p className='resolution'>{screenWidth}x{screenHeight}</p>
|
<p className='resolution'>{screenWidth}x{screenHeight}</p>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
:null
|
</If>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<video
|
<video
|
||||||
|
|
@ -72,13 +67,6 @@ export default class ScreenView extends React.Component
|
||||||
autoPlay
|
autoPlay
|
||||||
muted={Boolean(true)}
|
muted={Boolean(true)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{screenProfile === 'none' ?
|
|
||||||
<div className='spinner-container'>
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
:null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import NewWindow from './NewWindow';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as appPropTypes from '../appPropTypes';
|
import * as appPropTypes from '../appPropTypes';
|
||||||
import * as stateActions from '../../redux/stateActions';
|
import * as stateActions from '../../redux/stateActions';
|
||||||
import FullView from '../FullView';
|
import FullView from '../VideoContainers/FullView';
|
||||||
|
|
||||||
const VideoWindow = (props) =>
|
const VideoWindow = (props) =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -76,3 +76,16 @@ export const Message = PropTypes.shape(
|
||||||
text : PropTypes.string,
|
text : PropTypes.string,
|
||||||
sender : PropTypes.string
|
sender : PropTypes.string
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const FileEntryProps = PropTypes.shape(
|
||||||
|
{
|
||||||
|
data : PropTypes.shape({
|
||||||
|
name : PropTypes.string.isRequired,
|
||||||
|
picture : PropTypes.string,
|
||||||
|
file : PropTypes.shape({
|
||||||
|
magnet : PropTypes.string.isRequired
|
||||||
|
}).isRequired,
|
||||||
|
me : PropTypes.bool
|
||||||
|
}).isRequired,
|
||||||
|
notify : PropTypes.func.isRequired
|
||||||
|
});
|
||||||
|
|
@ -7,8 +7,9 @@ import { getDeviceInfo } from 'mediasoup-client';
|
||||||
import randomString from 'random-string';
|
import randomString from 'random-string';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
import * as utils from './utils';
|
import * as utils from './utils';
|
||||||
|
import RoomClient from './RoomClient';
|
||||||
|
import RoomContext from './RoomContext';
|
||||||
import * as cookiesManager from './cookiesManager';
|
import * as cookiesManager from './cookiesManager';
|
||||||
import * as requestActions from './redux/requestActions';
|
|
||||||
import * as stateActions from './redux/stateActions';
|
import * as stateActions from './redux/stateActions';
|
||||||
import Room from './components/Room';
|
import Room from './components/Room';
|
||||||
import { loginEnabled } from '../config';
|
import { loginEnabled } from '../config';
|
||||||
|
|
@ -16,6 +17,10 @@ import { store } from './store';
|
||||||
|
|
||||||
const logger = new Logger();
|
const logger = new Logger();
|
||||||
|
|
||||||
|
let roomClient;
|
||||||
|
|
||||||
|
RoomClient.init({ store });
|
||||||
|
|
||||||
domready(() =>
|
domready(() =>
|
||||||
{
|
{
|
||||||
logger.debug('DOM ready');
|
logger.debug('DOM ready');
|
||||||
|
|
@ -93,39 +98,38 @@ function run()
|
||||||
device.version = undefined;
|
device.version = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: I don't like this.
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
stateActions.setRoomUrl(roomUrl));
|
stateActions.setRoomUrl(roomUrl));
|
||||||
|
|
||||||
// NOTE: I don't like this.
|
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
stateActions.setMe({ peerName, displayName, displayNameSet, device, loginEnabled }));
|
stateActions.setMe({ peerName, displayName, displayNameSet, device, loginEnabled }));
|
||||||
|
|
||||||
// NOTE: I don't like this.
|
roomClient = new RoomClient(
|
||||||
store.dispatch(
|
{ roomId, peerName, displayName, device, useSimulcast, produce });
|
||||||
requestActions.joinRoom(
|
|
||||||
{ roomId, peerName, displayName, device, useSimulcast, produce }));
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<RoomContext.Provider value={roomClient}>
|
||||||
<Room />
|
<Room />
|
||||||
|
</RoomContext.Provider>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
document.getElementById('multiparty-meeting')
|
document.getElementById('multiparty-meeting')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Debugging stuff.
|
// TODO: Debugging stuff.
|
||||||
|
global.CLIENT = roomClient;
|
||||||
|
|
||||||
setInterval(() =>
|
setInterval(() =>
|
||||||
{
|
{
|
||||||
if (!global.CLIENT._room.peers[0])
|
if (!roomClient._room.peers[0])
|
||||||
{
|
{
|
||||||
delete global.CONSUMER;
|
delete global.CONSUMER;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peer = global.CLIENT._room.peers[0];
|
const peer = roomClient._room.peers[0];
|
||||||
|
|
||||||
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
|
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
@ -134,20 +138,20 @@ global.sendSdp = function()
|
||||||
{
|
{
|
||||||
logger.debug('---------- SEND_TRANSPORT LOCAL SDP OFFER:');
|
logger.debug('---------- SEND_TRANSPORT LOCAL SDP OFFER:');
|
||||||
logger.debug(
|
logger.debug(
|
||||||
global.CLIENT._sendTransport._handler._pc.localDescription.sdp);
|
roomClient._sendTransport._handler._pc.localDescription.sdp);
|
||||||
|
|
||||||
logger.debug('---------- SEND_TRANSPORT REMOTE SDP ANSWER:');
|
logger.debug('---------- SEND_TRANSPORT REMOTE SDP ANSWER:');
|
||||||
logger.debug(
|
logger.debug(
|
||||||
global.CLIENT._sendTransport._handler._pc.remoteDescription.sdp);
|
roomClient._sendTransport._handler._pc.remoteDescription.sdp);
|
||||||
};
|
};
|
||||||
|
|
||||||
global.recvSdp = function()
|
global.recvSdp = function()
|
||||||
{
|
{
|
||||||
logger.debug('---------- RECV_TRANSPORT REMOTE SDP OFFER:');
|
logger.debug('---------- RECV_TRANSPORT REMOTE SDP OFFER:');
|
||||||
logger.debug(
|
logger.debug(
|
||||||
global.CLIENT._recvTransport._handler._pc.remoteDescription.sdp);
|
roomClient._recvTransport._handler._pc.remoteDescription.sdp);
|
||||||
|
|
||||||
logger.debug('---------- RECV_TRANSPORT LOCAL SDP ANSWER:');
|
logger.debug('---------- RECV_TRANSPORT LOCAL SDP ANSWER:');
|
||||||
logger.debug(
|
logger.debug(
|
||||||
global.CLIENT._recvTransport._handler._pc.localDescription.sdp);
|
roomClient._recvTransport._handler._pc.localDescription.sdp);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
const files = (state = {}, action) =>
|
||||||
|
{
|
||||||
|
switch (action.type)
|
||||||
|
{
|
||||||
|
case 'ADD_FILE':
|
||||||
|
{
|
||||||
|
const { file } = action.payload;
|
||||||
|
|
||||||
|
const newFile = {
|
||||||
|
active : false,
|
||||||
|
progress : 0,
|
||||||
|
files : null,
|
||||||
|
me : false,
|
||||||
|
...file
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...state, [file.magnetUri]: newFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'ADD_FILE_HISTORY':
|
||||||
|
{
|
||||||
|
const { fileHistory } = action.payload;
|
||||||
|
const newFileHistory = {};
|
||||||
|
|
||||||
|
fileHistory.map((file) =>
|
||||||
|
{
|
||||||
|
const newFile = {
|
||||||
|
active : false,
|
||||||
|
progress : 0,
|
||||||
|
files : null,
|
||||||
|
me : false,
|
||||||
|
...file
|
||||||
|
};
|
||||||
|
|
||||||
|
newFileHistory[file.magnetUri] = newFile;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...state, ...newFileHistory };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_FILE_ACTIVE':
|
||||||
|
{
|
||||||
|
const { magnetUri } = action.payload;
|
||||||
|
const file = state[magnetUri];
|
||||||
|
|
||||||
|
const newFile = { ...file, active: true };
|
||||||
|
|
||||||
|
return { ...state, [magnetUri]: newFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_FILE_INACTIVE':
|
||||||
|
{
|
||||||
|
const { magnetUri } = action.payload;
|
||||||
|
const file = state[magnetUri];
|
||||||
|
|
||||||
|
const newFile = { ...file, active: false };
|
||||||
|
|
||||||
|
return { ...state, [magnetUri]: newFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_FILE_PROGRESS':
|
||||||
|
{
|
||||||
|
const { magnetUri, progress } = action.payload;
|
||||||
|
const file = state[magnetUri];
|
||||||
|
|
||||||
|
const newFile = { ...file, progress: progress };
|
||||||
|
|
||||||
|
return { ...state, [magnetUri]: newFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_FILE_DONE':
|
||||||
|
{
|
||||||
|
const { magnetUri, sharedFiles } = action.payload;
|
||||||
|
const file = state[magnetUri];
|
||||||
|
|
||||||
|
const newFile = {
|
||||||
|
...file,
|
||||||
|
files : sharedFiles,
|
||||||
|
progress : 1,
|
||||||
|
active : false,
|
||||||
|
timeout : false
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...state, [magnetUri]: newFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'REMOVE_FILE':
|
||||||
|
{
|
||||||
|
const { magnetUri } = action.payload;
|
||||||
|
|
||||||
|
return state.filter((file) => file.magnetUri !== magnetUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default files;
|
||||||
|
|
@ -8,7 +8,7 @@ import notifications from './notifications';
|
||||||
import chatmessages from './chatmessages';
|
import chatmessages from './chatmessages';
|
||||||
import chatbehavior from './chatbehavior';
|
import chatbehavior from './chatbehavior';
|
||||||
import toolarea from './toolarea';
|
import toolarea from './toolarea';
|
||||||
import sharing from './sharing';
|
import files from './files';
|
||||||
|
|
||||||
const reducers = combineReducers(
|
const reducers = combineReducers(
|
||||||
{
|
{
|
||||||
|
|
@ -21,7 +21,7 @@ const reducers = combineReducers(
|
||||||
chatmessages,
|
chatmessages,
|
||||||
chatbehavior,
|
chatbehavior,
|
||||||
toolarea,
|
toolarea,
|
||||||
sharing
|
files
|
||||||
});
|
});
|
||||||
|
|
||||||
export default reducers;
|
export default reducers;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
const initialState = [];
|
const notifications = (state = [], action) =>
|
||||||
|
|
||||||
const notifications = (state = initialState, action) =>
|
|
||||||
{
|
{
|
||||||
switch (action.type)
|
switch (action.type)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const initialState =
|
||||||
url : null,
|
url : null,
|
||||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||||
activeSpeakerName : null,
|
activeSpeakerName : null,
|
||||||
|
torrentSupport : false,
|
||||||
showSettings : false,
|
showSettings : false,
|
||||||
advancedMode : false,
|
advancedMode : false,
|
||||||
fullScreenConsumer : null, // ConsumerID
|
fullScreenConsumer : null, // ConsumerID
|
||||||
|
|
@ -41,6 +42,13 @@ const room = (state = initialState, action) =>
|
||||||
return { ...state, activeSpeakerName: peerName };
|
return { ...state, activeSpeakerName: peerName };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'FILE_SHARING_SUPPORTED':
|
||||||
|
{
|
||||||
|
const { supported } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, torrentSupport: supported };
|
||||||
|
}
|
||||||
|
|
||||||
case 'TOGGLE_SETTINGS':
|
case 'TOGGLE_SETTINGS':
|
||||||
{
|
{
|
||||||
const showSettings = !state.showSettings;
|
const showSettings = !state.showSettings;
|
||||||
|
|
|
||||||
|
|
@ -1,233 +1,5 @@
|
||||||
import randomString from 'random-string';
|
import randomString from 'random-string';
|
||||||
import * as stateActions from './stateActions';
|
import * as stateActions from './stateActions';
|
||||||
import
|
|
||||||
{
|
|
||||||
createNewMessage
|
|
||||||
} from './reducers/helper';
|
|
||||||
|
|
||||||
export const joinRoom = (
|
|
||||||
{ roomId, peerName, displayName, device, useSimulcast, produce }) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'JOIN_ROOM',
|
|
||||||
payload : { roomId, peerName, displayName, device, useSimulcast, produce }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const leaveRoom = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'LEAVE_ROOM'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeDisplayName = (displayName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'CHANGE_DISPLAY_NAME',
|
|
||||||
payload : { displayName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const muteMic = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'MUTE_MIC'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unmuteMic = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'UNMUTE_MIC'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const enableWebcam = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'ENABLE_WEBCAM'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const disableWebcam = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'DISABLE_WEBCAM'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeWebcam = (deviceId) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'CHANGE_WEBCAM',
|
|
||||||
payload : { deviceId }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeAudioDevice = (deviceId) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'CHANGE_AUDIO_DEVICE',
|
|
||||||
payload : { deviceId }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const enableAudioOnly = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'ENABLE_AUDIO_ONLY'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const disableAudioOnly = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'DISABLE_AUDIO_ONLY'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mutePeerAudio = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'MUTE_PEER_AUDIO',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unmutePeerAudio = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'UNMUTE_PEER_AUDIO',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pausePeerVideo = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'PAUSE_PEER_VIDEO',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resumePeerVideo = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'RESUME_PEER_VIDEO',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pausePeerScreen = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'PAUSE_PEER_SCREEN',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resumePeerScreen = (peerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'RESUME_PEER_SCREEN',
|
|
||||||
payload : { peerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const userLogin = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'USER_LOGIN'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const userLogout = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'USER_LOGOUT'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const raiseHand = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'RAISE_HAND'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const lowerHand = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'LOWER_HAND'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const restartIce = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'RESTART_ICE'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const enableScreenSharing = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'ENABLE_SCREEN_SHARING'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const disableScreenSharing = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'DISABLE_SCREEN_SHARING'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const installExtension = () =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'INSTALL_EXTENSION'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toggleHand = (enable) =>
|
|
||||||
{
|
|
||||||
if (enable)
|
|
||||||
return {
|
|
||||||
type : 'RAISE_HAND'
|
|
||||||
};
|
|
||||||
else
|
|
||||||
return {
|
|
||||||
type : 'LOWER_HAND'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendChatMessage = (text, name, picture) =>
|
|
||||||
{
|
|
||||||
const message = createNewMessage(text, 'response', name, picture);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type : 'SEND_CHAT_MESSAGE',
|
|
||||||
payload : { message }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendFile = (file, name, picture) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'SEND_FILE',
|
|
||||||
payload : { file, name, picture }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setSelectedPeer = (selectedPeerName) =>
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
type : 'REQUEST_SELECTED_PEER',
|
|
||||||
payload : { selectedPeerName }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// This returns a redux-thunk action (a function).
|
// This returns a redux-thunk action (a function).
|
||||||
export const notify = ({ type = 'info', text, timeout }) =>
|
export const notify = ({ type = 'info', text, timeout }) =>
|
||||||
|
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
import RoomClient from '../RoomClient';
|
|
||||||
|
|
||||||
export default ({ dispatch, getState }) => (next) =>
|
|
||||||
{
|
|
||||||
let client;
|
|
||||||
|
|
||||||
return (action) =>
|
|
||||||
{
|
|
||||||
switch (action.type)
|
|
||||||
{
|
|
||||||
case 'JOIN_ROOM':
|
|
||||||
{
|
|
||||||
const {
|
|
||||||
roomId,
|
|
||||||
peerName,
|
|
||||||
displayName,
|
|
||||||
device,
|
|
||||||
useSimulcast,
|
|
||||||
produce
|
|
||||||
} = action.payload;
|
|
||||||
|
|
||||||
client = new RoomClient(
|
|
||||||
{
|
|
||||||
roomId,
|
|
||||||
peerName,
|
|
||||||
displayName,
|
|
||||||
device,
|
|
||||||
useSimulcast,
|
|
||||||
produce,
|
|
||||||
dispatch,
|
|
||||||
getState
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: TMP
|
|
||||||
global.CLIENT = client;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'LEAVE_ROOM':
|
|
||||||
{
|
|
||||||
client.close();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'CHANGE_DISPLAY_NAME':
|
|
||||||
{
|
|
||||||
const { displayName } = action.payload;
|
|
||||||
|
|
||||||
client.changeDisplayName(displayName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'MUTE_MIC':
|
|
||||||
{
|
|
||||||
client.muteMic();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'UNMUTE_MIC':
|
|
||||||
{
|
|
||||||
client.unmuteMic();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ENABLE_WEBCAM':
|
|
||||||
{
|
|
||||||
client.enableWebcam();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'DISABLE_WEBCAM':
|
|
||||||
{
|
|
||||||
client.disableWebcam();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'CHANGE_WEBCAM':
|
|
||||||
{
|
|
||||||
const { deviceId } = action.payload;
|
|
||||||
|
|
||||||
client.changeWebcam(deviceId);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'CHANGE_AUDIO_DEVICE':
|
|
||||||
{
|
|
||||||
const { deviceId } = action.payload;
|
|
||||||
|
|
||||||
client.changeAudioDevice(deviceId);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ENABLE_AUDIO_ONLY':
|
|
||||||
{
|
|
||||||
client.enableAudioOnly();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'DISABLE_AUDIO_ONLY':
|
|
||||||
{
|
|
||||||
client.disableAudioOnly();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'MUTE_PEER_AUDIO':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.mutePeerAudio(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'UNMUTE_PEER_AUDIO':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.unmutePeerAudio(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'PAUSE_PEER_VIDEO':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.pausePeerVideo(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RESUME_PEER_VIDEO':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.resumePeerVideo(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'PAUSE_PEER_SCREEN':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.pausePeerScreen(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RESUME_PEER_SCREEN':
|
|
||||||
{
|
|
||||||
const { peerName } = action.payload;
|
|
||||||
|
|
||||||
client.resumePeerScreen(peerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RAISE_HAND':
|
|
||||||
{
|
|
||||||
client.sendRaiseHandState(true);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'USER_LOGIN':
|
|
||||||
{
|
|
||||||
client.login();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'USER_LOGOUT':
|
|
||||||
{
|
|
||||||
client.logout();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'LOWER_HAND':
|
|
||||||
{
|
|
||||||
client.sendRaiseHandState(false);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RESTART_ICE':
|
|
||||||
{
|
|
||||||
client.restartIce();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ENABLE_SCREEN_SHARING':
|
|
||||||
{
|
|
||||||
client.enableScreenSharing();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'DISABLE_SCREEN_SHARING':
|
|
||||||
{
|
|
||||||
client.disableScreenSharing();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'INSTALL_EXTENSION':
|
|
||||||
{
|
|
||||||
client.installExtension();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SEND_CHAT_MESSAGE':
|
|
||||||
{
|
|
||||||
const { message } = action.payload;
|
|
||||||
|
|
||||||
client.sendChatMessage(message);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'SEND_FILE':
|
|
||||||
{
|
|
||||||
client.sendFile(action.payload);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'REQUEST_SELECTED_PEER':
|
|
||||||
{
|
|
||||||
const { selectedPeerName } = action.payload;
|
|
||||||
|
|
||||||
client.setSelectedPeer(selectedPeerName);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -70,6 +70,14 @@ export const setWebcamDevices = (devices) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setFileSharingSupported = (supported) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'FILE_SHARING_SUPPORTED',
|
||||||
|
payload : { supported }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setDisplayName = (displayName) =>
|
export const setDisplayName = (displayName) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
@ -455,11 +463,11 @@ export const dropMessages = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addFile = (payload) =>
|
export const addFile = (file) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
type : 'ADD_FILE',
|
type : 'ADD_FILE',
|
||||||
payload
|
payload : { file }
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -471,6 +479,38 @@ export const addFileHistory = (fileHistory) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setFileActive = (magnetUri) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_FILE_ACTIVE',
|
||||||
|
payload : { magnetUri }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFileInActive = (magnetUri) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_FILE_INACTIVE',
|
||||||
|
payload : { magnetUri }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFileProgress = (magnetUri, progress) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_FILE_PROGRESS',
|
||||||
|
payload : { magnetUri, progress }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFileDone = (magnetUri, sharedFiles) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_FILE_DONE',
|
||||||
|
payload : { magnetUri, sharedFiles }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setPicture = (picture) =>
|
export const setPicture = (picture) =>
|
||||||
({
|
({
|
||||||
type : 'SET_PICTURE',
|
type : 'SET_PICTURE',
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import {
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import reducers from './redux/reducers';
|
import reducers from './redux/reducers';
|
||||||
import roomClientMiddleware from './redux/roomClientMiddleware';
|
|
||||||
|
|
||||||
const reduxMiddlewares =
|
const reduxMiddlewares =
|
||||||
[
|
[
|
||||||
thunk,
|
thunk
|
||||||
roomClientMiddleware
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
if (process.env.NODE_ENV === 'development')
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
"@babel/plugin-transform-runtime": "^7.1.0",
|
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@babel/preset-env": "^7.1.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"babel-plugin-jsx-control-statements": "^3.2.8",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babelify": "^10.0.0",
|
"babelify": "^10.0.0",
|
||||||
"browser-sync": "^2.26.3",
|
"browser-sync": "^2.26.3",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
> .client, > .response {
|
> .client, > .response {
|
||||||
background-color: rgba(#000, 0.1);
|
background-color: var(--chat-message-color);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
max-width: 85%;
|
max-width: 85%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -70,8 +70,8 @@
|
||||||
|
|
||||||
[data-component='Sender'] {
|
[data-component='Sender'] {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #fff;
|
background-color: var(--chat-input-bg-color);
|
||||||
color: #000;
|
color: var(--chat-input-text-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
|
@ -84,7 +84,6 @@
|
||||||
margin-right: 1vmin;
|
margin-right: 1vmin;
|
||||||
border-radius: 0.5vmin;
|
border-radius: 0.5vmin;
|
||||||
padding-left: 1vmin;
|
padding-left: 1vmin;
|
||||||
color: #000;
|
|
||||||
|
|
||||||
&.focus {
|
&.focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -95,8 +94,7 @@
|
||||||
width: 20%;
|
width: 20%;
|
||||||
box-shadow: 0vmin 0vmin 1vmin 0vmin rgba(17,17,17,0.5);
|
box-shadow: 0vmin 0vmin 1vmin 0vmin rgba(17,17,17,0.5);
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: #aef;
|
background-color: var(--chat-send-bg-color);
|
||||||
color: #000;
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border-radius: 0.5vmin;
|
border-radius: 0.5vmin;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,15 @@
|
||||||
> .share-file {
|
> .share-file {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #aef;
|
background: var(--filesharing-bg-color);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 1vmin;
|
border-radius: 1vmin;
|
||||||
box-shadow: 0vmin 0vmin 1vmin 0vmin rgba(17,17,17,0.5);
|
box-shadow: 0vmin 0vmin 1vmin 0vmin rgba(17,17,17,0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,13 +63,13 @@
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
> .film-content {
|
> .film-content {
|
||||||
border-color: #FFF;
|
border-color: var(--active-speaker-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
> .film-content {
|
> .film-content {
|
||||||
border-color: #377EFF;
|
border-color: var(--selected-peer-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,37 +69,4 @@
|
||||||
filter: blur(5px);
|
filter: blur(5px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .spinner-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(#000, 0.75);
|
|
||||||
|
|
||||||
.react-spinner {
|
|
||||||
position: relative;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
.react-spinner_bar {
|
|
||||||
position: absolute;
|
|
||||||
width: 20%;
|
|
||||||
height: 7.8%;
|
|
||||||
top: -3.9%;
|
|
||||||
left: -10%;
|
|
||||||
animation: PeerView-spinner 1.2s linear infinite;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: rgba(#fff, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes FullView-spinner {
|
|
||||||
0% { opacity: 1; }
|
|
||||||
100% { opacity: 0.15; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[data-component='HiddenPeersView'] {
|
[data-component='HiddenPeers'] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
background-position: bottom;
|
background-position: bottom;
|
||||||
background-size: auto 85%;
|
background-size: auto 85%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
border: var(--peer-border);
|
||||||
|
box-shadow: var(--peer-shadow);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 1.8vmin;
|
line-height: 1.8vmin;
|
||||||
|
|
@ -39,55 +41,28 @@
|
||||||
animation: none;
|
animation: none;
|
||||||
|
|
||||||
&.pulse {
|
&.pulse {
|
||||||
animation: pulse 2s;
|
animation: pulse 0.5s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-container>p{
|
.view-container>p{
|
||||||
transform: translate(0%,50%);
|
transform: translate(0%,50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-container,
|
|
||||||
.view-container::before,
|
|
||||||
.view-container::after {
|
|
||||||
/* Add shadow to distinguish sheets from one another */
|
|
||||||
box-shadow: 2px 1px 1px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-container::before,
|
|
||||||
.view-container::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #2a4b58;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Second sheet of paper */
|
|
||||||
.view-container::before {
|
|
||||||
left: .7vmin;
|
|
||||||
top: .7vmin;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Third sheet of paper */
|
|
||||||
.view-container::after {
|
|
||||||
left: 1.4vmin;
|
|
||||||
top: 1.4vmin;
|
|
||||||
z-index: -2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% {
|
0%
|
||||||
box-shadow: 0 0 0 0 rgba(255, 255, 255, 1.0);
|
{
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
50%
|
||||||
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0);
|
{
|
||||||
|
transform: scale3d(1.2, 1.2, 1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100%
|
||||||
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
|
{
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
[data-component='Logo'] {
|
[data-component='Logo'] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 4%;
|
width: var(--logo-width);
|
||||||
width: 8%;
|
height: var(--logo-height);
|
||||||
top: 1%;
|
top: 1%;
|
||||||
left: 1%;
|
left: 1%;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
background-position: left;
|
background-position: left;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
+desktop() {
|
+desktop() {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
+mobile() {
|
background-image: var(--logo);
|
||||||
}
|
|
||||||
background-image: url('/resources/images/logo.svg');
|
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
> .view-container {
|
> .view-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 20vmin;
|
width: var(--me-width);
|
||||||
height: 15vmin;
|
height: var(--me-height);
|
||||||
|
|
||||||
&.webcam {
|
&.webcam {
|
||||||
order: 2;
|
order: 2;
|
||||||
|
|
@ -23,9 +23,7 @@
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction:; row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.4vmin;
|
padding: 0.4vmin;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
|
|
@ -41,28 +39,20 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 75%;
|
background-size: 75%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: rgba(#000, 0.5);
|
background-color: var(--media-control-button-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
width: var(--media-control-button-size);
|
||||||
&.visible {
|
height: var(--media-control-button-size);
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
+desktop() {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
&.visible {
|
||||||
width: 22px;
|
opacity: 0.85;
|
||||||
height: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -71,11 +61,15 @@
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
background-color: var(--media-control-botton-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
background-color: rgba(#fff, 0.7);
|
background-color: var(--media-control-botton-on);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-color: var(--media-control-botton-off);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mic {
|
&.mic {
|
||||||
|
|
@ -85,7 +79,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -100,33 +93,12 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
background-image: url('/resources/images/icon_webcam_white_unsupported.svg');
|
background-image: url('/resources/images/icon_webcam_white_unsupported.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.screen {
|
|
||||||
&.on {
|
|
||||||
background-image: url('/resources/images/share-screen-black.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&.off {
|
|
||||||
background-image: url('/resources/images/no-share-screen-white.svg');
|
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unsupported {
|
|
||||||
background-image: url('/resources/images/no-share-screen-white.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fullscreen {
|
|
||||||
background-image: url('/resources/images/icon_fullscreen_black.svg');
|
|
||||||
background-color: rgba(#fff, 0.7);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.info {
|
&.info {
|
||||||
background-color: rgba(#0a1d26, 0.75);
|
background-color: var(--notification-info-bg-color);
|
||||||
color: rgba(#fff, 0.65);
|
color: var(--notification-info-text-color);
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
|
|
@ -94,8 +94,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
background-color: rgba(#ff1914, 0.65);
|
background-color: var(--notification-error-bg-color);
|
||||||
color: rgba(#fff, 0.85);
|
color: var(--notification-error-text-color);
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
|
|
||||||
|
|
@ -51,21 +51,13 @@
|
||||||
background-color: rgba(#000, 0.5);
|
background-color: rgba(#000, 0.5);
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
width: var(--media-control-button-size);
|
||||||
+desktop() {
|
height: var(--media-control-button-size);
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
@ -102,19 +94,20 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
|
||||||
&.level0 { height: 0; background-color: rgba(#000, 0.8); }
|
&.level0 { height: 0; }
|
||||||
&.level1 { height: 0.2vh; background-color: rgba(#000, 0.8); }
|
&.level1 { height: 0.2vh; }
|
||||||
&.level2 { height: 0.4vh; background-color: rgba(#000, 0.8); }
|
&.level2 { height: 0.4vh; }
|
||||||
&.level3 { height: 0.6vh; background-color: rgba(#000, 0.8); }
|
&.level3 { height: 0.6vh; }
|
||||||
&.level4 { height: 0.8vh; background-color: rgba(#000, 0.8); }
|
&.level4 { height: 0.8vh; }
|
||||||
&.level5 { height: 1.0vh; background-color: rgba(#000, 0.8); }
|
&.level5 { height: 1.0vh; }
|
||||||
&.level6 { height: 1.2vh; background-color: rgba(#000, 0.8); }
|
&.level6 { height: 1.2vh; }
|
||||||
&.level7 { height: 1.4vh; background-color: rgba(#000, 0.8); }
|
&.level7 { height: 1.4vh; }
|
||||||
&.level8 { height: 1.6vh; background-color: rgba(#000, 0.8); }
|
&.level8 { height: 1.6vh; }
|
||||||
&.level9 { height: 1.8vh; background-color: rgba(#000, 0.8); }
|
&.level9 { height: 1.8vh; }
|
||||||
&.level10 { height: 2.0vh; background-color: rgba(#000, 0.8); }
|
&.level10 { height: 2.0vh; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .controls {
|
> .controls {
|
||||||
float: right;
|
float: right;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -133,21 +126,13 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
width: var(--media-control-button-size);
|
||||||
+desktop() {
|
height: var(--media-control-button-size);
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
@ -155,11 +140,15 @@
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
background-color: var(--media-control-botton-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
background-color: rgba(#fff, 0.7);
|
background-color: var(--media-control-botton-on);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-color: var(--media-control-botton-off);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mic {
|
&.mic {
|
||||||
|
|
@ -169,7 +158,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -184,7 +172,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -199,7 +186,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/no-share-screen-white.svg');
|
background-image: url('/resources/images/no-share-screen-white.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
|
||||||
|
|
@ -54,21 +54,13 @@
|
||||||
background-color: rgba(#000, 0.5);
|
background-color: rgba(#000, 0.5);
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
width: var(--media-control-button-size);
|
||||||
+desktop() {
|
height: var(--media-control-button-size);
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
@ -107,25 +99,17 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 75%;
|
background-size: 75%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: rgba(#000, 0.5);
|
background-color: var(--media-control-button-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
width: var(--media-control-button-size);
|
||||||
+desktop() {
|
height: var(--media-control-button-size);
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
@ -133,11 +117,15 @@
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
background-color: var(--media-control-botton-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
background-color: rgba(#fff, 0.7);
|
background-color: var(--media-control-botton-on);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-color: var(--media-control-botton-off);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mic {
|
&.mic {
|
||||||
|
|
@ -147,7 +135,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
background-image: url('/resources/images/icon_remote_mic_white_off.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -155,21 +142,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.webcam {
|
|
||||||
&.on {
|
|
||||||
background-image: url('/resources/images/icon_webcam_black_on.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
&.off {
|
|
||||||
background-image: url('/resources/images/icon_remote_webcam_white_off.svg');
|
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unsupported {
|
|
||||||
background-image: url('/resources/images/icon_webcam_white_unsupported.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.screen {
|
&.screen {
|
||||||
&.on {
|
&.on {
|
||||||
background-image: url('/resources/images/share-screen-black.svg');
|
background-image: url('/resources/images/share-screen-black.svg');
|
||||||
|
|
@ -177,7 +149,6 @@
|
||||||
|
|
||||||
&.off {
|
&.off {
|
||||||
background-image: url('/resources/images/no-share-screen-white.svg');
|
background-image: url('/resources/images/no-share-screen-white.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
|
|
@ -187,12 +158,10 @@
|
||||||
|
|
||||||
&.fullscreen {
|
&.fullscreen {
|
||||||
background-image: url('/resources/images/icon_fullscreen_black.svg');
|
background-image: url('/resources/images/icon_fullscreen_black.svg');
|
||||||
background-color: rgba(#fff, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.newwindow {
|
&.newwindow {
|
||||||
background-image: url('/resources/images/icon_new_window_black.svg');
|
background-image: url('/resources/images/icon_new_window_black.svg');
|
||||||
background-color: rgba(#fff, 0.7);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: rgba(#2a4b58, 0.9);
|
background-color: var(--peer-bg-color);
|
||||||
background-image: url('/resources/images/buddy.svg');
|
background-image: var(--peer-empty-avatar);
|
||||||
background-position: bottom;
|
background-position: bottom;
|
||||||
background-size: auto 85%;
|
background-size: auto 85%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
$backgroundTint = #000;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
top: 0.6vmin;
|
top: 0.6vmin;
|
||||||
|
|
@ -178,7 +176,7 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
transition-duration: .15s;
|
transition-duration: .15s;
|
||||||
background-color: rgba(#000, 0.75);
|
background-color: var(--peer-video-bg-color);
|
||||||
|
|
||||||
&.is-me {
|
&.is-me {
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
|
|
@ -225,37 +223,4 @@
|
||||||
&.level10 { height: 100%; background-color: rgba(#000, 0.65); }
|
&.level10 { height: 100%; background-color: rgba(#000, 0.65); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .spinner-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(#000, 0.75);
|
|
||||||
|
|
||||||
.react-spinner {
|
|
||||||
position: relative;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
.react-spinner_bar {
|
|
||||||
position: absolute;
|
|
||||||
width: 20%;
|
|
||||||
height: 7.8%;
|
|
||||||
top: -3.9%;
|
|
||||||
left: -10%;
|
|
||||||
animation: PeerView-spinner 1.2s linear infinite;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: rgba(#fff, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes PeerView-spinner {
|
|
||||||
0% { opacity: 1; }
|
|
||||||
100% { opacity: 0.15; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,18 +31,18 @@
|
||||||
+desktop() {
|
+desktop() {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
border: 1px solid rgba(#fff, 0.15);
|
border: var(--peer-border);
|
||||||
box-shadow: 0px 5px 12px 2px rgba(#111, 0.5);
|
box-shadow: var(--peer-shadow);
|
||||||
transition-property: border-color;
|
transition-property: border-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
|
||||||
&.active-speaker {
|
&.active-speaker {
|
||||||
border-color: #fff;
|
border-color: var(--active-speaker-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
> .peer-content {
|
> .peer-content {
|
||||||
border: 1px solid #377eff;
|
border-color: var(--selected-peer-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,24 +143,15 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0px 5px 12px 2px rgba(#111, 0.5);
|
box-shadow: var(--me-shadow);
|
||||||
transition-property: border-color;
|
transition-property: border-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
|
||||||
&.active-speaker {
|
|
||||||
border-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
+desktop() {
|
|
||||||
top: 6%;
|
top: 6%;
|
||||||
left:1%;
|
left:1%;
|
||||||
border: 1px solid rgba(#fff, 0.15);
|
border: var(--me-border);
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
&.active-speaker {
|
||||||
top: 6%;
|
border-color: var(--active-speaker-border-color);
|
||||||
left: 1%;
|
|
||||||
border: 1px solid rgba(#fff, 0.25);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: rgba(#2a4b58, 0.9);
|
background-color: var(--peer-bg-color);
|
||||||
background-image: url('/resources/images/buddy.svg');
|
background-image: var(--peer-empty-avatar);
|
||||||
background-position: bottom;
|
background-position: bottom;
|
||||||
background-size: auto 85%;
|
background-size: auto 85%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
$backgroundTint = #000;
|
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
top: 0.6vmin;
|
top: 0.6vmin;
|
||||||
|
|
@ -58,7 +56,7 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
transition-duration: .15s;
|
transition-duration: .15s;
|
||||||
background-color: rgba(#000, 0.75);
|
background-color: var(--peer-video-bg-color);
|
||||||
|
|
||||||
&.is-me {
|
&.is-me {
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
|
|
@ -73,37 +71,4 @@
|
||||||
filter: blur(5px);
|
filter: blur(5px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .spinner-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(#000, 0.75);
|
|
||||||
|
|
||||||
.react-spinner {
|
|
||||||
position: relative;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
.react-spinner_bar {
|
|
||||||
position: absolute;
|
|
||||||
width: 20%;
|
|
||||||
height: 7.8%;
|
|
||||||
top: -3.9%;
|
|
||||||
left: -10%;
|
|
||||||
animation: PeerView-spinner 1.2s linear infinite;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: rgba(#fff, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ScreenView-spinner {
|
|
||||||
0% { opacity: 1; }
|
|
||||||
100% { opacity: 0.15; }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[data-component='Settings'] {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
[data-component='Sidebar'] {
|
[data-component='Sidebar'] {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
top: calc(50% - 60px);
|
top: 50%;
|
||||||
height: 120px;
|
transform: translate(0%, -50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 75%;
|
background-size: 75%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: rgba(#fff, 0.3);
|
background-color: var(--circle-button-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
|
|
@ -32,24 +32,20 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
height: var(--circle-button-size);
|
||||||
+desktop() {
|
width: var(--circle-button-size);
|
||||||
height: 2.5em;
|
|
||||||
width: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
height: 2.5em;
|
|
||||||
width: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
background-color: rgba(#fff, 0.7);
|
background-color: var(--circle-button-toggled-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsupported {
|
||||||
|
background-color: var(--circle-button-unsupported-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
background-color: var(--circle-button-diabled-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.login {
|
&.login {
|
||||||
|
|
@ -93,7 +89,6 @@
|
||||||
|
|
||||||
&.unsupported {
|
&.unsupported {
|
||||||
background-image: url('/resources/images/no-share-screen-white.svg');
|
background-image: url('/resources/images/no-share-screen-white.svg');
|
||||||
background-color: rgba(#d42241, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.need-extension {
|
&.need-extension {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component='ToolAreaButton'] {
|
|
||||||
&.on {
|
|
||||||
right: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component='ToolArea'] {
|
[data-component='ToolArea'] {
|
||||||
&.open {
|
&.open {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
|
@ -33,35 +27,6 @@
|
||||||
.toolarea-shade.open {
|
.toolarea-shade.open {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
> .button {
|
|
||||||
background-position: center;
|
|
||||||
background-size: 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-color: rgba(#aef);
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 15%;
|
|
||||||
padding: 1px;
|
|
||||||
|
|
||||||
+desktop() {
|
|
||||||
height: 36px;
|
|
||||||
width: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
height: 32px;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
&.toolarea-close-button {
|
|
||||||
background-image: url('/resources/images/arrow_right.svg');
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: -22px;
|
|
||||||
display: none;
|
|
||||||
&.on {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .toolarea-button {
|
> .toolarea-button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -153,12 +118,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
[data-component='ToolAreaButton'] {
|
|
||||||
&.on {
|
|
||||||
right: 60%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component='ToolArea'] {
|
[data-component='ToolArea'] {
|
||||||
&.open {
|
&.open {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
@ -167,12 +126,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 900px) {
|
@media (min-width: 900px) {
|
||||||
[data-component='ToolAreaButton'] {
|
|
||||||
&.on {
|
|
||||||
right: 40%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component='ToolArea'] {
|
[data-component='ToolArea'] {
|
||||||
&.open {
|
&.open {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
|
@ -181,12 +134,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1500px) {
|
@media (min-width: 1500px) {
|
||||||
[data-component='ToolAreaButton'] {
|
|
||||||
&.on {
|
|
||||||
right: 25%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component='ToolArea'] {
|
[data-component='ToolArea'] {
|
||||||
&.open {
|
&.open {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
|
@ -194,79 +141,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-component='ToolAreaButton'] {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1020;
|
|
||||||
right: 0;
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
margin: 2rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
transition: right 0.3s;
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin: 4px 0;
|
|
||||||
background-position: center;
|
|
||||||
background-size: 75%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-color: rgba(#fff, 0.3);
|
|
||||||
cursor: pointer;
|
|
||||||
transition-property: opacity, background-color;
|
|
||||||
transition-duration: 0.15s;
|
|
||||||
border-radius: 100%;
|
|
||||||
|
|
||||||
+desktop() {
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
+mobile() {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.on {
|
|
||||||
background-color: rgba(#fff, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.toolarea-button {
|
|
||||||
background-image: url('/resources/images/icon_tool_area_white.svg');
|
|
||||||
|
|
||||||
&.on {
|
|
||||||
background-image: url('/resources/images/icon_tool_area_black.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .badge {
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 1rem;
|
|
||||||
background: #b12525;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: -8px;
|
|
||||||
line-height: 1rem;
|
|
||||||
margin-right: -8px;
|
|
||||||
position: absolute;
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
&.long {
|
|
||||||
border-radius: 25% / 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component='ToolArea'] {
|
[data-component='ToolArea'] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,53 @@ global-reset();
|
||||||
@import './reset';
|
@import './reset';
|
||||||
@import './keyframes';
|
@import './keyframes';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--logo: url('/resources/images/logo.svg');
|
||||||
|
--logo-width: 8%;
|
||||||
|
--logo-height: 4%;
|
||||||
|
|
||||||
|
--background: url('/resources/images/background.svg');
|
||||||
|
--background-color: rgba(51, 51, 51, 1.0);
|
||||||
|
|
||||||
|
--circle-button-color: rgba(255, 255, 255, 0.3);
|
||||||
|
--circle-button-toggled-color: rgba(255, 255, 255, 0.7);
|
||||||
|
--circle-button-unsupported-color: rgba(212, 34, 65, 0.7);
|
||||||
|
--circle-button-diabled-color: rgba(255, 255, 255, 0.5);
|
||||||
|
--circle-button-size: 2.5em;
|
||||||
|
|
||||||
|
--media-control-button-color: rgba(255, 255, 255, 0.85);
|
||||||
|
--media-control-botton-on: rgba(255, 255, 255, 0.7);
|
||||||
|
--media-control-botton-off: rgba(212, 34, 65, 0.7);
|
||||||
|
--media-control-botton-disabled: rgba(255, 255, 255, 0.5)
|
||||||
|
--media-control-button-size: 1.5em;
|
||||||
|
|
||||||
|
--me-shadow: 0px 5px 12px 2px rgba(17, 17, 17, 0.5);
|
||||||
|
--me-border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
--me-width: 20vmin;
|
||||||
|
--me-height: 15vmin;
|
||||||
|
|
||||||
|
--peer-shadow: 0px 5px 12px 2px rgba(17, 17, 17, 0.5);
|
||||||
|
--peer-border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
--peer-empty-avatar: url('/resources/images/buddy.svg');
|
||||||
|
--peer-bg-color: rgba(42, 75, 88, 0.9);
|
||||||
|
--peer-video-bg-color: rgba(0, 0, 0, 0.75);
|
||||||
|
|
||||||
|
--chat-message-color: rgba(0, 0, 0, 0.1);
|
||||||
|
--chat-input-bg-color: rgba(255, 255, 255, 1.0);
|
||||||
|
--chat-input-text-color: rgba(0, 0, 0, 1.0);
|
||||||
|
--chat-send-bg-color: rgba(170, 238, 255, 1.0);
|
||||||
|
|
||||||
|
--filesharing-bg-color: rgba(170, 238, 255, 1.0);
|
||||||
|
|
||||||
|
--notification-info-bg-color: rgba(10, 29, 38, 0.75);
|
||||||
|
--notification-info-text-color: rgba(255, 255, 255, 0.65);
|
||||||
|
--notification-error-bg-color: rgba(255, 25, 20, 0.65);
|
||||||
|
--notification-error-text-color: rgba(255, 255, 255, 0.85);
|
||||||
|
|
||||||
|
--active-speaker-border-color: rgba(255, 255, 255, 1.0);
|
||||||
|
--selected-peer-border-color: rgba(55, 126, 255, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
|
|
@ -23,11 +70,11 @@ body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
background-color: #333;
|
background-color: var(--background-color);
|
||||||
|
|
||||||
+desktop() {
|
+desktop() {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-image: url('/resources/images/background.svg');
|
background-image: var(--background);
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
@ -48,11 +95,10 @@ body {
|
||||||
@import './components/Peers';
|
@import './components/Peers';
|
||||||
@import './components/Peer';
|
@import './components/Peer';
|
||||||
@import './components/PeerView';
|
@import './components/PeerView';
|
||||||
@import './components/HiddenPeersView';
|
@import './components/HiddenPeers';
|
||||||
@import './components/ScreenView';
|
@import './components/ScreenView';
|
||||||
@import './components/Notifications';
|
@import './components/Notifications';
|
||||||
@import './components/Chat';
|
@import './components/Chat';
|
||||||
@import './components/Settings';
|
|
||||||
@import './components/ToolArea';
|
@import './components/ToolArea';
|
||||||
@import './components/ParticipantList';
|
@import './components/ParticipantList';
|
||||||
@import './components/FullScreenView';
|
@import './components/FullScreenView';
|
||||||
|
|
|
||||||
401
app/test/DATA.js
401
app/test/DATA.js
|
|
@ -1,401 +0,0 @@
|
||||||
/* eslint-disable key-spacing */
|
|
||||||
|
|
||||||
exports.ROOM_OPTIONS =
|
|
||||||
{
|
|
||||||
requestTimeout: 10000,
|
|
||||||
transportOptions:
|
|
||||||
{
|
|
||||||
tcp: false
|
|
||||||
},
|
|
||||||
__turnServers:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
urls: [ 'turn:worker2.versatica.com:3478?transport=udp' ],
|
|
||||||
username: 'testuser1',
|
|
||||||
credential: 'testpasswd1'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
hidden: false
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.ROOM_RTP_CAPABILITIES =
|
|
||||||
{
|
|
||||||
codecs:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'PCMA',
|
|
||||||
mimeType: 'audio/PCMA',
|
|
||||||
kind: 'audio',
|
|
||||||
clockRate: 8000,
|
|
||||||
preferredPayloadType: 8,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'opus',
|
|
||||||
mimeType: 'audio/opus',
|
|
||||||
kind: 'audio',
|
|
||||||
clockRate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
preferredPayloadType: 96,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'SILK',
|
|
||||||
mimeType: 'audio/SILK',
|
|
||||||
kind: 'audio',
|
|
||||||
clockRate: 16000,
|
|
||||||
preferredPayloadType: 97,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'VP9',
|
|
||||||
mimeType: 'video/VP9',
|
|
||||||
kind: 'video',
|
|
||||||
clockRate: 90000,
|
|
||||||
preferredPayloadType: 102,
|
|
||||||
rtcpFeedback:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'pli',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'goog-remb'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'bar',
|
|
||||||
type: 'foo'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rtx',
|
|
||||||
mimeType: 'video/rtx',
|
|
||||||
kind: 'video',
|
|
||||||
clockRate: 90000,
|
|
||||||
preferredPayloadType: 103,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {
|
|
||||||
apt: 102
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'VP8',
|
|
||||||
mimeType: 'video/VP8',
|
|
||||||
kind: 'video',
|
|
||||||
clockRate: 90000,
|
|
||||||
preferredPayloadType: 100,
|
|
||||||
rtcpFeedback:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'pli',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'goog-remb'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'bar',
|
|
||||||
type: 'foo'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rtx',
|
|
||||||
mimeType: 'video/rtx',
|
|
||||||
kind: 'video',
|
|
||||||
clockRate: 90000,
|
|
||||||
preferredPayloadType: 101,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {
|
|
||||||
apt: 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
headerExtensions: [
|
|
||||||
{
|
|
||||||
kind: 'audio',
|
|
||||||
uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
|
|
||||||
preferredId: 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'video',
|
|
||||||
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
|
|
||||||
preferredId: 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'video',
|
|
||||||
uri: 'http://foo.bar',
|
|
||||||
preferredId: 12
|
|
||||||
}
|
|
||||||
],
|
|
||||||
fecMechanisms: []
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.QUERY_ROOM_RESPONSE =
|
|
||||||
{
|
|
||||||
rtpCapabilities: exports.ROOM_RTP_CAPABILITIES
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.JOIN_ROOM_RESPONSE =
|
|
||||||
{
|
|
||||||
peers:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'alice',
|
|
||||||
appData: 'Alice iPad Pro',
|
|
||||||
consumers:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 3333,
|
|
||||||
kind: 'audio',
|
|
||||||
paused: false,
|
|
||||||
appData: 'ALICE_MIC',
|
|
||||||
rtpParameters:
|
|
||||||
{
|
|
||||||
muxId: null,
|
|
||||||
codecs:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'PCMA',
|
|
||||||
mimeType: 'audio/PCMA',
|
|
||||||
clockRate: 8000,
|
|
||||||
payloadType: 8,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
headerExtensions:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
|
|
||||||
id: 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
encodings:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ssrc: 33333333
|
|
||||||
}
|
|
||||||
],
|
|
||||||
rtcp:
|
|
||||||
{
|
|
||||||
cname: 'ALICECNAME',
|
|
||||||
reducedSize: true,
|
|
||||||
mux: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bob',
|
|
||||||
appData: 'Bob HP Laptop',
|
|
||||||
consumers:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
id: 6666,
|
|
||||||
kind: 'audio',
|
|
||||||
paused: false,
|
|
||||||
appData: 'BOB_MIC',
|
|
||||||
rtpParameters:
|
|
||||||
{
|
|
||||||
muxId: null,
|
|
||||||
codecs:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'opus',
|
|
||||||
mimeType: 'audio/opus',
|
|
||||||
clockRate: 48000,
|
|
||||||
channels: 2,
|
|
||||||
payloadType: 96,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
headerExtensions:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',
|
|
||||||
id: 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
encodings:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ssrc: 66666666
|
|
||||||
}
|
|
||||||
],
|
|
||||||
rtcp:
|
|
||||||
{
|
|
||||||
cname: 'BOBCNAME',
|
|
||||||
reducedSize: true,
|
|
||||||
mux: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.CREATE_TRANSPORT_1_RESPONSE =
|
|
||||||
{
|
|
||||||
iceParameters:
|
|
||||||
{
|
|
||||||
usernameFragment: 'server-usernamefragment-12345678',
|
|
||||||
password: 'server-password-xxxxxxxx',
|
|
||||||
iceLite: true
|
|
||||||
},
|
|
||||||
iceCandidates:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
foundation: 'F1',
|
|
||||||
priority: 1234,
|
|
||||||
ip: '1.2.3.4',
|
|
||||||
protocol: 'udp',
|
|
||||||
port: 9999,
|
|
||||||
type: 'host'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dtlsParameters:
|
|
||||||
{
|
|
||||||
fingerprints:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
algorithm: 'sha-256',
|
|
||||||
value: 'FF:FF:39:66:A4:E2:66:60:30:18:A7:59:B3:AF:A5:33:58:5E:7F:69:A4:62:A6:D4:EB:9F:B7:42:05:35:FF:FF'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
role: 'client'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.CREATE_TRANSPORT_2_RESPONSE =
|
|
||||||
{
|
|
||||||
iceParameters:
|
|
||||||
{
|
|
||||||
usernameFragment: 'server-usernamefragment-12345678',
|
|
||||||
password: 'server-password-xxxxxxxx',
|
|
||||||
iceLite: true
|
|
||||||
},
|
|
||||||
iceCandidates:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
foundation: 'F1',
|
|
||||||
priority: 1234,
|
|
||||||
ip: '1.2.3.4',
|
|
||||||
protocol: 'udp',
|
|
||||||
port: 9999,
|
|
||||||
type: 'host'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
dtlsParameters:
|
|
||||||
{
|
|
||||||
fingerprints:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
algorithm: 'sha-256',
|
|
||||||
value: 'FF:FF:39:66:A4:E2:66:60:30:18:A7:59:B3:AF:A5:33:58:5E:7F:69:A4:62:A6:D4:EB:9F:B7:42:05:35:FF:FF'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
role: 'auto'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.ALICE_WEBCAM_NEW_CONSUMER_NOTIFICATION =
|
|
||||||
{
|
|
||||||
method: 'newConsumer',
|
|
||||||
notification: true,
|
|
||||||
id: 4444,
|
|
||||||
peerName: 'alice',
|
|
||||||
kind: 'video',
|
|
||||||
paused: true,
|
|
||||||
appData: 'ALICE_WEBCAM',
|
|
||||||
rtpParameters:
|
|
||||||
{
|
|
||||||
muxId: null,
|
|
||||||
codecs:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'VP8',
|
|
||||||
mimeType: 'video/VP8',
|
|
||||||
clockRate: 90000,
|
|
||||||
payloadType: 100,
|
|
||||||
rtcpFeedback:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'pli',
|
|
||||||
type: 'nack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: '',
|
|
||||||
type: 'goog-remb'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parameter: 'bar',
|
|
||||||
type: 'foo'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
parameters: {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rtx',
|
|
||||||
mimeType: 'video/rtx',
|
|
||||||
clockRate: 90000,
|
|
||||||
payloadType: 101,
|
|
||||||
rtcpFeedback: [],
|
|
||||||
parameters: {
|
|
||||||
apt: 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
headerExtensions:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
kind: 'video',
|
|
||||||
uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',
|
|
||||||
id: 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: 'video',
|
|
||||||
uri: 'http://foo.bar',
|
|
||||||
id: 12
|
|
||||||
}
|
|
||||||
],
|
|
||||||
encodings:
|
|
||||||
[
|
|
||||||
{
|
|
||||||
ssrc: 444444441,
|
|
||||||
rtx: {
|
|
||||||
ssrc: 444444442
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
rtcp:
|
|
||||||
{
|
|
||||||
cname: 'ALICECNAME',
|
|
||||||
reducedSize: true,
|
|
||||||
mux: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
const path = require('path');
|
|
||||||
const gulp = require('gulp');
|
|
||||||
const gutil = require('gulp-util');
|
|
||||||
const plumber = require('gulp-plumber');
|
|
||||||
const rename = require('gulp-rename');
|
|
||||||
const browserify = require('browserify');
|
|
||||||
const watchify = require('watchify');
|
|
||||||
const envify = require('envify/custom');
|
|
||||||
const source = require('vinyl-source-stream');
|
|
||||||
const buffer = require('vinyl-buffer');
|
|
||||||
const eslint = require('gulp-eslint');
|
|
||||||
const browserSync = require('browser-sync');
|
|
||||||
|
|
||||||
const OUTPUT_DIR = 'output';
|
|
||||||
const APP_NAME = 'mediasoup-client-test';
|
|
||||||
|
|
||||||
// Node environment.
|
|
||||||
process.env.NODE_ENV = 'development';
|
|
||||||
|
|
||||||
function logError(error)
|
|
||||||
{
|
|
||||||
gutil.log(gutil.colors.red(error.stack));
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('lint', () =>
|
|
||||||
{
|
|
||||||
const src =
|
|
||||||
[
|
|
||||||
'gulpfile.js',
|
|
||||||
'**/*.js',
|
|
||||||
'**/*.jsx'
|
|
||||||
];
|
|
||||||
|
|
||||||
return gulp.src(src)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(eslint())
|
|
||||||
.pipe(eslint.format());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('html', () =>
|
|
||||||
{
|
|
||||||
return gulp.src('index.html')
|
|
||||||
.pipe(gulp.dest(OUTPUT_DIR));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('bundle', () =>
|
|
||||||
{
|
|
||||||
const watch = true;
|
|
||||||
|
|
||||||
let bundler = browserify(
|
|
||||||
{
|
|
||||||
entries : 'index.jsx',
|
|
||||||
extensions : [ '.js', '.jsx' ],
|
|
||||||
// required for sourcemaps (must be false otherwise).
|
|
||||||
debug : process.env.NODE_ENV === 'development',
|
|
||||||
// required for watchify.
|
|
||||||
cache : {},
|
|
||||||
// required for watchify.
|
|
||||||
packageCache : {},
|
|
||||||
// required to be true only for watchify.
|
|
||||||
fullPaths : watch
|
|
||||||
})
|
|
||||||
.transform('babelify',
|
|
||||||
{
|
|
||||||
presets : [ 'es2015', 'es2017', 'react' ],
|
|
||||||
plugins :
|
|
||||||
[
|
|
||||||
'transform-runtime',
|
|
||||||
'transform-object-assign',
|
|
||||||
'transform-object-rest-spread'
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.transform(envify(
|
|
||||||
{
|
|
||||||
NODE_ENV : process.env.NODE_ENV,
|
|
||||||
_ : 'purge'
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (watch)
|
|
||||||
{
|
|
||||||
bundler = watchify(bundler);
|
|
||||||
|
|
||||||
bundler.on('update', () =>
|
|
||||||
{
|
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
gutil.log('bundling...');
|
|
||||||
rebundle();
|
|
||||||
gutil.log('bundle took %sms', (Date.now() - start));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function rebundle()
|
|
||||||
{
|
|
||||||
return bundler.bundle()
|
|
||||||
.on('error', logError)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(source(`${APP_NAME}.js`))
|
|
||||||
.pipe(buffer())
|
|
||||||
.pipe(rename(`${APP_NAME}.js`))
|
|
||||||
.pipe(gulp.dest(OUTPUT_DIR));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rebundle();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('livebrowser', (done) =>
|
|
||||||
{
|
|
||||||
browserSync(
|
|
||||||
{
|
|
||||||
server :
|
|
||||||
{
|
|
||||||
baseDir : OUTPUT_DIR
|
|
||||||
},
|
|
||||||
ghostMode : false,
|
|
||||||
files : path.join(OUTPUT_DIR, '**', '*')
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('watch', (done) =>
|
|
||||||
{
|
|
||||||
// Watch changes in HTML.
|
|
||||||
gulp.watch([ 'index.html' ], gulp.series(
|
|
||||||
'html'
|
|
||||||
));
|
|
||||||
|
|
||||||
// Watch changes in JS files.
|
|
||||||
gulp.watch([ 'gulpfile.js', '**/*.js', '**/*.jsx' ], gulp.series(
|
|
||||||
'lint'
|
|
||||||
));
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('live', gulp.series(
|
|
||||||
'lint',
|
|
||||||
'html',
|
|
||||||
'bundle',
|
|
||||||
'watch',
|
|
||||||
'livebrowser'
|
|
||||||
));
|
|
||||||
|
|
||||||
gulp.task('default', gulp.series('live'));
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>mediasoup-client test</title>
|
|
||||||
<meta charset='UTF-8'>
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'>
|
|
||||||
<meta name='description' content='mediasoup-client test'>
|
|
||||||
|
|
||||||
<script async src='/mediasoup-client-test.js'></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>mediasoup-client test</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,692 +0,0 @@
|
||||||
import * as mediasoupClient from 'mediasoup-client';
|
|
||||||
import domready from 'domready';
|
|
||||||
import Logger from '../lib/Logger';
|
|
||||||
const DATA = require('./DATA');
|
|
||||||
|
|
||||||
window.mediasoupClient = mediasoupClient;
|
|
||||||
|
|
||||||
const logger = new Logger();
|
|
||||||
|
|
||||||
|
|
||||||
const SEND = true;
|
|
||||||
const SEND_AUDIO = true;
|
|
||||||
const SEND_VIDEO = false;
|
|
||||||
const RECV = true;
|
|
||||||
|
|
||||||
|
|
||||||
domready(() =>
|
|
||||||
{
|
|
||||||
logger.debug('DOM ready');
|
|
||||||
|
|
||||||
run();
|
|
||||||
});
|
|
||||||
|
|
||||||
function run()
|
|
||||||
{
|
|
||||||
logger.debug('run() [environment:%s]', process.env.NODE_ENV);
|
|
||||||
|
|
||||||
let transport1;
|
|
||||||
let transport2;
|
|
||||||
let audioTrack;
|
|
||||||
let videoTrack;
|
|
||||||
let audioProducer1;
|
|
||||||
let audioProducer2;
|
|
||||||
let videoProducer;
|
|
||||||
|
|
||||||
logger.debug('calling room = new mediasoupClient.Room()');
|
|
||||||
|
|
||||||
// const room = new mediasoupClient.Room();
|
|
||||||
const room = new mediasoupClient.Room(DATA.ROOM_OPTIONS);
|
|
||||||
|
|
||||||
window.room = room;
|
|
||||||
|
|
||||||
room.on('closed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'room "closed" event [originator:%s, appData:%o]', originator, appData);
|
|
||||||
});
|
|
||||||
|
|
||||||
room.on('request', (request, callback, errback) =>
|
|
||||||
{
|
|
||||||
logger.warn('sending request [method:%s]:%o', request.method, request);
|
|
||||||
|
|
||||||
switch (request.method)
|
|
||||||
{
|
|
||||||
case 'queryRoom':
|
|
||||||
{
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
callback(DATA.QUERY_ROOM_RESPONSE);
|
|
||||||
errback('upppps');
|
|
||||||
}, 200);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'joinRoom':
|
|
||||||
{
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
callback(DATA.JOIN_ROOM_RESPONSE);
|
|
||||||
// errback('upppps');
|
|
||||||
}, 200);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'createTransport':
|
|
||||||
{
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
switch (request.appData)
|
|
||||||
{
|
|
||||||
case 'TRANSPORT_1':
|
|
||||||
callback(DATA.CREATE_TRANSPORT_1_RESPONSE);
|
|
||||||
break;
|
|
||||||
case 'TRANSPORT_2':
|
|
||||||
callback(DATA.CREATE_TRANSPORT_2_RESPONSE);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errback('upppps');
|
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'createProducer':
|
|
||||||
{
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}, 250);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'enableConsumer':
|
|
||||||
{
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}, 500);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
errback(`NO IDEA ABOUT REQUEST METHOD "${request.method}"`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
room.on('notify', (notification) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'sending notification [method:%s]:%o', notification.method, notification);
|
|
||||||
|
|
||||||
switch (notification.method)
|
|
||||||
{
|
|
||||||
case 'leaveRoom':
|
|
||||||
case 'updateTransport':
|
|
||||||
case 'closeTransport':
|
|
||||||
case 'closeProducer':
|
|
||||||
case 'pauseProducer':
|
|
||||||
case 'resumeProducer':
|
|
||||||
case 'pauseConsumer':
|
|
||||||
case 'resumeConsumer':
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
logger.error(`NO IDEA ABOUT NOTIFICATION METHOD "${notification.method}"`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
room.on('newpeer', (peer) =>
|
|
||||||
{
|
|
||||||
logger.warn('room "newpeer" event [name:"%s", peer:%o]', peer.name, peer);
|
|
||||||
|
|
||||||
handlePeer(peer);
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.resolve()
|
|
||||||
.then(() =>
|
|
||||||
{
|
|
||||||
logger.debug('calling room.join()');
|
|
||||||
|
|
||||||
const deviceInfo = mediasoupClient.getDeviceInfo();
|
|
||||||
const appData =
|
|
||||||
{
|
|
||||||
device : `${deviceInfo.name} ${deviceInfo.version}`
|
|
||||||
};
|
|
||||||
|
|
||||||
return room.join(null, appData);
|
|
||||||
// return room.join(DATA.ROOM_RTP_CAPABILITIES, appData);
|
|
||||||
})
|
|
||||||
.then((peers) =>
|
|
||||||
{
|
|
||||||
if (!RECV)
|
|
||||||
return;
|
|
||||||
|
|
||||||
logger.debug('room.join() succeeded');
|
|
||||||
|
|
||||||
logger.debug('calling transport2 = room.createTransport("recv")');
|
|
||||||
|
|
||||||
transport2 = room.createTransport('recv', 'TRANSPORT_2');
|
|
||||||
window.transport2 = transport2;
|
|
||||||
window.pc2 = transport2._handler._pc;
|
|
||||||
|
|
||||||
handleTransport(transport2);
|
|
||||||
|
|
||||||
for (const peer of peers)
|
|
||||||
{
|
|
||||||
handlePeer(peer);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() =>
|
|
||||||
{
|
|
||||||
if (!SEND)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (room.canSend('audio'))
|
|
||||||
logger.debug('can send audio');
|
|
||||||
else
|
|
||||||
logger.warn('cannot send audio');
|
|
||||||
|
|
||||||
if (room.canSend('video'))
|
|
||||||
logger.debug('can send video');
|
|
||||||
else
|
|
||||||
logger.warn('cannot send video');
|
|
||||||
|
|
||||||
logger.debug('calling transport1 = room.createTransport("send")');
|
|
||||||
|
|
||||||
transport1 = room.createTransport('send', 'TRANSPORT_1');
|
|
||||||
window.transport1 = transport1;
|
|
||||||
window.pc1 = transport1._handler._pc;
|
|
||||||
|
|
||||||
handleTransport(transport1);
|
|
||||||
|
|
||||||
logger.debug('calling getUserMedia()');
|
|
||||||
|
|
||||||
return navigator.mediaDevices
|
|
||||||
.getUserMedia({ audio: SEND_AUDIO, video: SEND_VIDEO });
|
|
||||||
})
|
|
||||||
.then((stream) =>
|
|
||||||
{
|
|
||||||
if (!SEND)
|
|
||||||
return;
|
|
||||||
|
|
||||||
audioTrack = stream.getAudioTracks()[0];
|
|
||||||
videoTrack = stream.getVideoTracks()[0];
|
|
||||||
window.audioTrack = audioTrack;
|
|
||||||
window.videoTrack = videoTrack;
|
|
||||||
})
|
|
||||||
// Add Producers.
|
|
||||||
.then(() =>
|
|
||||||
{
|
|
||||||
if (audioTrack)
|
|
||||||
{
|
|
||||||
const deviceId = audioTrack.getSettings().deviceId;
|
|
||||||
|
|
||||||
logger.debug('calling audioProducer1 = room.createProducer(audioTrack)');
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
audioProducer1 = room.createProducer(audioTrack, `${deviceId}-1`);
|
|
||||||
window.audioProducer1 = audioProducer1;
|
|
||||||
|
|
||||||
handleProducer(audioProducer1);
|
|
||||||
}
|
|
||||||
catch (error)
|
|
||||||
{
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('calling audioProducer2 = room.createProducer(audioTrack)');
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
audioProducer2 = room.createProducer(audioTrack, `${deviceId}-2`);
|
|
||||||
window.audioProducer2 = audioProducer2;
|
|
||||||
|
|
||||||
handleProducer(audioProducer2);
|
|
||||||
}
|
|
||||||
catch (error)
|
|
||||||
{
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoTrack)
|
|
||||||
{
|
|
||||||
const deviceId = videoTrack.getSettings().deviceId;
|
|
||||||
|
|
||||||
logger.debug('calling videoProducer = room.createProducer(videoTrack)');
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
videoProducer = room.createProducer(videoTrack, `${deviceId}-1`);
|
|
||||||
window.videoProducer = videoProducer;
|
|
||||||
|
|
||||||
handleProducer(videoProducer);
|
|
||||||
}
|
|
||||||
catch (error)
|
|
||||||
{
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Receive notifications.
|
|
||||||
.then(() =>
|
|
||||||
{
|
|
||||||
if (!RECV)
|
|
||||||
return;
|
|
||||||
|
|
||||||
setTimeout(() =>
|
|
||||||
{
|
|
||||||
room.receiveNotification(DATA.ALICE_WEBCAM_NEW_CONSUMER_NOTIFICATION);
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTransport(transport)
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'handleTransport() [direction:%s, appData:"%s", transport:%o]',
|
|
||||||
transport.direction, transport.appData, transport);
|
|
||||||
|
|
||||||
transport.on('closed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'transport "closed" event [originator:%s, appData:%o, transport:%o]',
|
|
||||||
originator, appData, transport);
|
|
||||||
});
|
|
||||||
|
|
||||||
transport.on('connectionstatechange', (state) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'transport "connectionstatechange" event [direction:%s, state:%s, transport:%o]',
|
|
||||||
transport.direction, state, transport);
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() =>
|
|
||||||
{
|
|
||||||
const queue = transport._commandQueue._queue;
|
|
||||||
|
|
||||||
if (queue.length !== 0)
|
|
||||||
logger.error('queue not empty [transport:%o, queue:%o]', transport, queue);
|
|
||||||
}, 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePeer(peer)
|
|
||||||
{
|
|
||||||
logger.warn('handlePeer() [name:"%s", peer:%o]', peer.name, peer);
|
|
||||||
|
|
||||||
switch (peer.name)
|
|
||||||
{
|
|
||||||
case 'alice':
|
|
||||||
window.alice = peer;
|
|
||||||
break;
|
|
||||||
case 'bob':
|
|
||||||
window.bob = peer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const consumer of peer.consumers)
|
|
||||||
{
|
|
||||||
handleConsumer(consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.on('closed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'peer "closed" event [name:"%s", originator:%s, appData:%o]',
|
|
||||||
peer.name, originator, appData);
|
|
||||||
});
|
|
||||||
|
|
||||||
peer.on('newconsumer', (consumer) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'peer "newconsumer" event [name:"%s", id:%s, consumer:%o]',
|
|
||||||
peer.name, consumer.id, consumer);
|
|
||||||
|
|
||||||
handleConsumer(consumer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleProducer(producer)
|
|
||||||
{
|
|
||||||
const transport1 = window.transport1;
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
'handleProducer() [id:"%s", appData:%o, producer:%o]',
|
|
||||||
producer.id, producer.appData, producer);
|
|
||||||
|
|
||||||
logger.debug('handleProducer() | calling transport1.send(producer)');
|
|
||||||
|
|
||||||
transport1.send(producer)
|
|
||||||
.then(() =>
|
|
||||||
{
|
|
||||||
logger.debug('transport1.send(producer) succeeded');
|
|
||||||
})
|
|
||||||
.catch((error) =>
|
|
||||||
{
|
|
||||||
logger.error('transport1.send(producer) failed: %o', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
producer.on('closed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'producer "closed" event [id:%s, originator:%s, appData:%o, producer:%o]',
|
|
||||||
producer.id, originator, appData, producer);
|
|
||||||
});
|
|
||||||
|
|
||||||
producer.on('paused', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'producer "paused" event [id:%s, originator:%s, appData:%o, producer:%o]',
|
|
||||||
producer.id, originator, appData, producer);
|
|
||||||
});
|
|
||||||
|
|
||||||
producer.on('resumed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'producer "resumed" event [id:%s, originator:%s, appData:%o, producer:%o]',
|
|
||||||
producer.id, originator, appData, producer);
|
|
||||||
});
|
|
||||||
|
|
||||||
producer.on('unhandled', () =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'producer "unhandled" event [id:%s, producer:%o]', producer.id, producer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleConsumer(consumer)
|
|
||||||
{
|
|
||||||
const transport2 = window.transport2;
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
'handleConsumer() [id:"%s", appData:%o, consumer:%o]',
|
|
||||||
consumer.id, consumer.appData, consumer);
|
|
||||||
|
|
||||||
switch (consumer.appData)
|
|
||||||
{
|
|
||||||
case 'ALICE_MIC':
|
|
||||||
window.aliceAudioConsumer = consumer;
|
|
||||||
break;
|
|
||||||
case 'ALICE_WEBCAM':
|
|
||||||
window.aliceVideoConsumer = consumer;
|
|
||||||
break;
|
|
||||||
case 'BOB_MIC':
|
|
||||||
window.bobAudioConsumer = consumer;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('handleConsumer() calling transport2.receive(consumer)');
|
|
||||||
|
|
||||||
transport2.receive(consumer)
|
|
||||||
.then((track) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'transport2.receive(consumer) succeeded [track:%o]', track);
|
|
||||||
})
|
|
||||||
.catch((error) =>
|
|
||||||
{
|
|
||||||
logger.error('transport2.receive() failed:%o', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
consumer.on('closed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'consumer "closed" event [id:%s, originator:%s, appData:%o, consumer:%o]',
|
|
||||||
consumer.id, originator, appData, consumer);
|
|
||||||
});
|
|
||||||
|
|
||||||
consumer.on('paused', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'consumer "paused" event [id:%s, originator:%s, appData:%o, consumer:%o]',
|
|
||||||
consumer.id, originator, appData, consumer);
|
|
||||||
});
|
|
||||||
|
|
||||||
consumer.on('resumed', (originator, appData) =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'consumer "resumed" event [id:%s, originator:%s, appData:%o, consumer:%o]',
|
|
||||||
consumer.id, originator, appData, consumer);
|
|
||||||
});
|
|
||||||
|
|
||||||
consumer.on('unhandled', () =>
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'consumer "unhandled" event [id:%s, consumer:%o]', consumer.id, consumer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Trigger server notifications.
|
|
||||||
|
|
||||||
window.notifyRoomClosed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'roomClosed',
|
|
||||||
notification : true,
|
|
||||||
appData : 'ha cascao la room remota!!!'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyTransportClosed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'transportClosed',
|
|
||||||
notification : true,
|
|
||||||
id : room.transports[0].id,
|
|
||||||
appData : 'admin closed your transport'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAudioProducer1Closed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'producerClosed',
|
|
||||||
notification : true,
|
|
||||||
id : window.audioProducer1.id,
|
|
||||||
appData : 'te paro el micro por la fuerza'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAudioProducer1Paused = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'producerPaused',
|
|
||||||
notification : true,
|
|
||||||
id : window.audioProducer1.id,
|
|
||||||
appData : 'te pause el micro por la fuerza'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAudioProducer1Resumed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'producerResumed',
|
|
||||||
notification : true,
|
|
||||||
id : window.audioProducer1.id,
|
|
||||||
appData : 'te resumo el micro'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAlicePeerClosed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'peerClosed',
|
|
||||||
notification : true,
|
|
||||||
name : 'alice',
|
|
||||||
appData : 'peer left'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAliceAudioConsumerClosed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'consumerClosed',
|
|
||||||
notification : true,
|
|
||||||
peerName : 'alice',
|
|
||||||
id : 3333,
|
|
||||||
appData : 'mic broken'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAliceVideoConsumerClosed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'consumerClosed',
|
|
||||||
notification : true,
|
|
||||||
peerName : 'alice',
|
|
||||||
id : 4444,
|
|
||||||
appData : 'webcam broken'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAliceVideoConsumerPaused = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'consumerPaused',
|
|
||||||
notification : true,
|
|
||||||
peerName : 'alice',
|
|
||||||
id : 4444,
|
|
||||||
appData : 'webcam paused'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.notifyAliceVideoConsumerResumed = function()
|
|
||||||
{
|
|
||||||
const room = window.room;
|
|
||||||
const notification =
|
|
||||||
{
|
|
||||||
method : 'consumerResumed',
|
|
||||||
notification : true,
|
|
||||||
peerName : 'alice',
|
|
||||||
id : 4444,
|
|
||||||
appData : 'webcam resumed'
|
|
||||||
};
|
|
||||||
|
|
||||||
room.receiveNotification(notification);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Test pause/resume.
|
|
||||||
|
|
||||||
window.testPauseResume = function()
|
|
||||||
{
|
|
||||||
logger.debug('testPauseResume() with audioProducer1');
|
|
||||||
|
|
||||||
const producer = window.audioProducer1;
|
|
||||||
|
|
||||||
// producer.once('paused', () =>
|
|
||||||
// {
|
|
||||||
// producer.resume('I RESUME TO FUACK!!!');
|
|
||||||
// });
|
|
||||||
|
|
||||||
logger.debug('testPauseResume() | (1) calling producer.pause()');
|
|
||||||
|
|
||||||
if (producer.pause('I PAUSE (1)'))
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'testPauseResume() | (1) producer.pause() succeeded [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.error(
|
|
||||||
'testPauseResume() | (1) producer.pause() failed [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('testPauseResume() | (2) calling producer.pause()');
|
|
||||||
|
|
||||||
if (producer.pause('I PAUSE (2)'))
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'testPauseResume() | (2) producer.pause() succeeded [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.error(
|
|
||||||
'testPauseResume() | (2) producer.pause() failed [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('testPauseResume() | (3) calling producer.resume()');
|
|
||||||
|
|
||||||
if (producer.resume('I RESUME (3)'))
|
|
||||||
{
|
|
||||||
logger.warn(
|
|
||||||
'testPauseResume() | (3) producer.resume() succeeded [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.error(
|
|
||||||
'testPauseResume() | (3) producer.resume() failed [locallyPaused:%s]',
|
|
||||||
producer.locallyPaused);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: For debugging.
|
|
||||||
|
|
||||||
window.dump1 = function()
|
|
||||||
{
|
|
||||||
const transport1 = window.transport1;
|
|
||||||
const pc1 = transport1._handler._pc;
|
|
||||||
|
|
||||||
if (pc1 && pc1.localDescription)
|
|
||||||
logger.warn('PC1 SEND LOCAL OFFER:\n%s', pc1.localDescription.sdp);
|
|
||||||
|
|
||||||
if (pc1 && pc1.remoteDescription)
|
|
||||||
logger.warn('PC1 SEND REMOTE ANSWER:\n%s', pc1.remoteDescription.sdp);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.dump2 = function()
|
|
||||||
{
|
|
||||||
const transport2 = window.transport2;
|
|
||||||
const pc2 = transport2._handler._pc;
|
|
||||||
|
|
||||||
if (pc2 && pc2.remoteDescription)
|
|
||||||
logger.warn('PC2 RECV REMOTE OFFER:\n%s', pc2.remoteDescription.sdp);
|
|
||||||
|
|
||||||
if (pc2 && pc2.localDescription)
|
|
||||||
logger.warn('PC2 RECV LOCAL ANSWER:\n%s', pc2.localDescription.sdp);
|
|
||||||
};
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>mediasoup-client test</title>
|
|
||||||
<meta charset='UTF-8'>
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no'>
|
|
||||||
<meta name='description' content='mediasoup-client test'>
|
|
||||||
|
|
||||||
<script async src='/mediasoup-client-test.js'></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>mediasoup-client test</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue