diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js
index bbdc1ba..fe54c3b 100644
--- a/app/lib/RoomClient.js
+++ b/app/lib/RoomClient.js
@@ -1,5 +1,8 @@
import io from 'socket.io-client';
import * as mediasoupClient from 'mediasoup-client';
+import WebTorrent from 'webtorrent';
+import createTorrent from 'create-torrent';
+import { saveAs } from 'file-saver/FileSaver';
import Logger from './Logger';
import hark from 'hark';
import ScreenShare from './ScreenShare';
@@ -61,6 +64,9 @@ export default class RoomClient
// Whether we should produce.
this._produce = produce;
+ // Torrent support
+ this._torrentSupport = WebTorrent.WEBRTC_SUPPORT;
+
// Whether simulcast should be used.
this._useSimulcast = useSimulcast;
@@ -83,6 +89,15 @@ export default class RoomClient
this._room = new mediasoupClient.Room(ROOM_OPTIONS);
this._room.roomId = roomId;
+ // Our WebTorrent client
+ this._webTorrent = this._torrentSupport && new WebTorrent({
+ tracker : {
+ rtcConfig : {
+ iceServers : ROOM_OPTIONS.turnServers
+ }
+ }
+ });
+
// Max spotlights
this._maxSpotlights = ROOM_OPTIONS.maxSpotlights;
@@ -344,7 +359,128 @@ export default class RoomClient
}
}
- async sendFile(file)
+ saveFile(file)
+ {
+ file.getBlob((err, blob) =>
+ {
+ if (err)
+ {
+ return this.props.notify({
+ text : 'An error occurred while saving a file'
+ });
+ }
+
+ saveAs(blob, file.name);
+ });
+ }
+
+ handleDownload(magnetUri)
+ {
+ store.dispatch(
+ stateActions.setFileActive(magnetUri));
+
+ const existingTorrent = this._webTorrent.get(magnetUri);
+
+ if (existingTorrent)
+ {
+ // Never add duplicate torrents, use the existing one instead.
+ return this._handleTorrent(existingTorrent);
+ }
+
+ this._webTorrent.add(magnetUri, this._handleTorrent);
+ }
+
+ _handleTorrent(torrent)
+ {
+ // Torrent already done, this can happen if the
+ // same file was sent multiple times.
+ if (torrent.progress === 1)
+ {
+
+ store.dispatch(
+ stateActions.setFileDone(
+ torrent.magnetURI,
+ torrent.files
+ ));
+
+ return;
+ }
+
+ torrent.on('download', () =>
+ {
+ store.dispatch(
+ stateActions.setFileProgress(
+ torrent.magnetURI,
+ torrent.progress
+ ));
+ });
+
+ torrent.on('done', () =>
+ {
+ store.dispatch(
+ stateActions.setFileDone(
+ torrent.magnetURI,
+ torrent.files
+ ));
+ });
+ }
+
+ async shareFiles(files)
+ {
+ this.notify('Creating torrent');
+
+ createTorrent(files, (err, torrent) =>
+ {
+ if (err)
+ {
+ return this.notify(
+ 'An error occured while uploading a file'
+ );
+ }
+
+ const existingTorrent = this._webTorrent.get(torrent);
+
+ if (existingTorrent)
+ {
+ const { displayName, picture } = store.getState().me;
+
+ const file = {
+ magnetUri : existingTorrent.magnetURI,
+ displayName,
+ picture
+ };
+
+ return this._sendFile(file);
+ }
+
+ this._webTorrent.seed(files, (newTorrent) =>
+ {
+ this.notify(
+ 'Torrent successfully created'
+ );
+
+ const { displayName, picture } = store.getState().me;
+ const file = {
+ magnetUri : newTorrent.magnetURI,
+ displayName,
+ picture
+ };
+
+ store.dispatch(stateActions.addFile(
+ {
+ magnetUri : file.magnetUri,
+ displayName : displayName,
+ picture : picture,
+ me : true
+ }));
+
+ this._sendFile(file);
+ });
+ });
+ }
+
+ // { file, name, picture }
+ async _sendFile(file)
{
logger.debug('sendFile() [file: %o]', file);
@@ -728,68 +864,6 @@ export default class RoomClient
stateActions.setWebcamInProgress(false));
}
- async changeWebcamResolution()
- {
- logger.debug('changeWebcamResolution()');
-
- let oldResolution;
- let newResolution;
-
- store.dispatch(
- stateActions.setWebcamInProgress(true));
-
- try
- {
- oldResolution = this._webcam.resolution;
-
- switch (oldResolution)
- {
- case 'qvga':
- newResolution = 'vga';
- break;
- case 'vga':
- newResolution = 'hd';
- break;
- case 'hd':
- newResolution = 'qvga';
- break;
- }
-
- this._webcam.resolution = newResolution;
-
- const { device } = this._webcam;
-
- logger.debug('changeWebcamResolution() | calling getUserMedia()');
-
- const stream = await navigator.mediaDevices.getUserMedia(
- {
- video :
- {
- deviceId : { exact: device.deviceId },
- ...VIDEO_CONSTRAINS
- }
- });
-
- const track = stream.getVideoTracks()[0];
-
- const newTrack = await this._webcamProducer.replaceTrack(track);
-
- track.stop();
-
- store.dispatch(
- stateActions.setProducerTrack(this._webcamProducer.id, newTrack));
- }
- catch (error)
- {
- logger.error('changeWebcamResolution() failed: %o', error);
-
- this._webcam.resolution = oldResolution;
- }
-
- store.dispatch(
- stateActions.setWebcamInProgress(false));
- }
-
setSelectedPeer(peerName)
{
logger.debug('setSelectedPeer() [peerName:"%s"]', peerName);
@@ -800,266 +874,59 @@ export default class RoomClient
stateActions.setSelectedPeer(peerName));
}
- async mutePeerAudio(peerName)
+ // type: mic/webcam/screen
+ // mute: true/false
+ modifyPeerConsumer(peerName, type, mute)
{
- logger.debug('mutePeerAudio() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerAudioInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'mic')
- continue;
-
- await consumer.pause('mute-audio');
- }
- }
- }
- }
- catch (error)
- {
- logger.error('mutePeerAudio() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerAudioInProgress(peerName, false));
- }
-
- async unmutePeerAudio(peerName)
- {
- logger.debug('unmutePeerAudio() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerAudioInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'mic' || !consumer.supported)
- continue;
-
- await consumer.resume();
- }
- }
- }
- }
- catch (error)
- {
- logger.error('unmutePeerAudio() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerAudioInProgress(peerName, false));
- }
-
- async pausePeerVideo(peerName)
- {
- logger.debug('pausePeerVideo() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerVideoInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'webcam')
- continue;
-
- await consumer.pause('pause-video');
- }
- }
- }
- }
- catch (error)
- {
- logger.error('pausePeerVideo() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerVideoInProgress(peerName, false));
- }
-
- async resumePeerVideo(peerName)
- {
- logger.debug('resumePeerVideo() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerVideoInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'webcam' || !consumer.supported)
- continue;
-
- await consumer.resume();
- }
- }
- }
- }
- catch (error)
- {
- logger.error('resumePeerVideo() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerVideoInProgress(peerName, false));
- }
-
- async pausePeerScreen(peerName)
- {
- logger.debug('pausePeerScreen() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerScreenInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'screen')
- continue;
-
- await consumer.pause('pause-screen');
- }
- }
- }
- }
- catch (error)
- {
- logger.error('pausePeerScreen() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerScreenInProgress(peerName, false));
- }
-
- async resumePeerScreen(peerName)
- {
- logger.debug('resumePeerScreen() [peerName:"%s"]', peerName);
-
- store.dispatch(
- stateActions.setPeerScreenInProgress(peerName, true));
-
- try
- {
- for (const peer of this._room.peers)
- {
- if (peer.name === peerName)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.appData.source !== 'screen' || !consumer.supported)
- continue;
-
- await consumer.resume();
- }
- }
- }
- }
- catch (error)
- {
- logger.error('resumePeerScreen() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setPeerScreenInProgress(peerName, false));
- }
-
- async enableAudioOnly()
- {
- logger.debug('enableAudioOnly()');
-
- store.dispatch(
- stateActions.setAudioOnlyInProgress(true));
-
- try
- {
- if (this._webcamProducer)
- await this._webcamProducer.close();
-
- for (const peer of this._room.peers)
- {
- for (const consumer of peer.consumers)
- {
- if (consumer.kind !== 'video')
- continue;
-
- await consumer.pause('audio-only-mode');
- }
- }
+ logger.debug(
+ 'modifyPeerConsumer() [peerName:"%s", type:"%s"]',
+ peerName,
+ type
+ );
+ if (type === 'mic')
store.dispatch(
- stateActions.setAudioOnlyState(true));
- }
- catch (error)
- {
- logger.error('enableAudioOnly() failed: %o', error);
- }
-
- store.dispatch(
- stateActions.setAudioOnlyInProgress(false));
- }
-
- async disableAudioOnly()
- {
- logger.debug('disableAudioOnly()');
-
- store.dispatch(
- stateActions.setAudioOnlyInProgress(true));
+ stateActions.setPeerAudioInProgress(peerName, true));
+ else if (type === 'webcam')
+ store.dispatch(
+ stateActions.setPeerVideoInProgress(peerName, true));
+ else if (type === 'screen')
+ store.dispatch(
+ stateActions.setPeerScreenInProgress(peerName, true));
try
{
- if (!this._webcamProducer && this._room.canSend('video'))
- await this.enableWebcam();
-
for (const peer of this._room.peers)
{
- for (const consumer of peer.consumers)
+ if (peer.name === peerName)
{
- if (consumer.kind !== 'video' || !consumer.supported)
- continue;
+ for (const consumer of peer.consumers)
+ {
+ if (consumer.appData.source !== type || !consumer.supported)
+ continue;
- await consumer.resume();
+ if (mute)
+ consumer.pause(`mute-${type}`);
+ else
+ consumer.resume();
+ }
}
}
-
- store.dispatch(
- stateActions.setAudioOnlyState(false));
}
catch (error)
{
- logger.error('disableAudioOnly() failed: %o', error);
+ logger.error('modifyPeerConsumer() failed: %o', error);
}
- store.dispatch(
- stateActions.setAudioOnlyInProgress(false));
+ if (type === 'mic')
+ store.dispatch(
+ stateActions.setPeerAudioInProgress(peerName, false));
+ else if (type === 'webcam')
+ store.dispatch(
+ stateActions.setPeerVideoInProgress(peerName, false));
+ else if (type === 'screen')
+ store.dispatch(
+ stateActions.setPeerScreenInProgress(peerName, false));
}
async sendRaiseHandState(state)
@@ -1338,6 +1205,9 @@ export default class RoomClient
{
await this._room.join(this._peerName, { displayName, device });
+ store.dispatch(
+ stateActions.setFileSharingSupported(this._torrentSupport));
+
this._sendTransport =
this._room.createTransport('send', { media: 'SEND_MIC_WEBCAM' });
diff --git a/app/lib/components/FileSharing/File.jsx b/app/lib/components/FileSharing/File.jsx
new file mode 100644
index 0000000..96f15d6
--- /dev/null
+++ b/app/lib/components/FileSharing/File.jsx
@@ -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 (
+
+

+
+
+
+
+ You shared a file.
+
+
+ {file.displayName} shared a file.
+
+
+
+
+
+
+
+
+ {
+ roomClient.handleDownload(file.magnetUri);
+ }}
+ >
+
+
+
+
+
+ Your browser does not support downloading files using WebTorrent.
+
+
+
+
{magnet.decode(file.magnetUri).dn}
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+ File finished downloading.
+
+ {file.files.map((sharedFile, i) => (
+
+
+ {
+ roomClient.saveFile(sharedFile);
+ }}
+ >
+
+
+
+
{sharedFile.name}
+
+ ))}
+
+
+
+
+ );
+ }
+}
+
+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));
\ No newline at end of file
diff --git a/app/lib/components/FileSharing/FileEntry.jsx b/app/lib/components/FileSharing/FileEntry.jsx
deleted file mode 100644
index 71ed6be..0000000
--- a/app/lib/components/FileSharing/FileEntry.jsx
+++ /dev/null
@@ -1,201 +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 (
-
-

-
-
-
-
- You shared a file.
-
-
- {this.props.data.name} shared a file.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Your browser does not support downloading files using WebTorrent.
-
-
-
-
{magnet.decode(this.props.data.file.magnet).dn}
-
-
-
-
-
-
- Locating peers
-
-
- {this.state.timeout && (
-
- 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.
-
- )}
-
-
-
-
0}>
-
-
-
-
-
- Torrent finished downloading.
-
- {this.state.files.map((file, i) => (
-
-
this.saveFile(file)}>
-
-
-
-
{file.name}
-
- ))}
-
-
-
-
- );
- }
-}
-
-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);
\ No newline at end of file
diff --git a/app/lib/components/FileSharing/FileList.jsx b/app/lib/components/FileSharing/FileList.jsx
new file mode 100644
index 0000000..9215e3a
--- /dev/null
+++ b/app/lib/components/FileSharing/FileList.jsx
@@ -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 '../Chat/scrollToBottom';
+import File from './File';
+
+class FileList extends Component
+{
+ render()
+ {
+ const {
+ files
+ } = this.props;
+
+ return (
+
+ { Object.keys(files).map((magnetUri) =>
+
+ )}
+
+ );
+ }
+}
+
+FileList.propTypes = {
+ files : PropTypes.object.isRequired
+};
+
+const mapStateToProps = (state) =>
+{
+ return {
+ files : state.files
+ };
+};
+
+export default compose(
+ connect(mapStateToProps),
+ scrollToBottom()
+)(FileList);
diff --git a/app/lib/components/FileSharing/FileSharing.jsx b/app/lib/components/FileSharing/FileSharing.jsx
new file mode 100644
index 0000000..84fccac
--- /dev/null
+++ b/app/lib/components/FileSharing/FileSharing.jsx
@@ -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 (
+
+
+
+
+
+ {buttonDescription}
+
+
+
+
+
+ );
+ }
+}
+
+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));
diff --git a/app/lib/components/FileSharing/SharedFilesList.jsx b/app/lib/components/FileSharing/SharedFilesList.jsx
deleted file mode 100644
index f4f18c8..0000000
--- a/app/lib/components/FileSharing/SharedFilesList.jsx
+++ /dev/null
@@ -1,60 +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 (
-
-
- 0}>
- {
- sharing.map((entry, i) => (
-
- ))
- }
-
-
-
-
No one has shared files yet...
-
-
-
-
- );
- }
-}
-
-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);
diff --git a/app/lib/components/FileSharing/index.jsx b/app/lib/components/FileSharing/index.jsx
deleted file mode 100644
index fd41051..0000000
--- a/app/lib/components/FileSharing/index.jsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
- {buttonDescription}
-
-
-
-
-
- );
- }
-}
-
-export default FileSharing;
\ No newline at end of file
diff --git a/app/lib/components/Filmstrip.jsx b/app/lib/components/Filmstrip.jsx
index 63a80fb..9ebe692 100644
--- a/app/lib/components/Filmstrip.jsx
+++ b/app/lib/components/Filmstrip.jsx
@@ -145,14 +145,11 @@ class Filmstrip extends Component
+ { Object.keys(peers).map((peerName) =>
{
- Object.keys(peers).map((peerName) =>
+ if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
{
-
spotlightsElement === peerName)
- }
- >
+ return (
roomClient.setSelectedPeer(peerName)}
@@ -168,9 +165,9 @@ class Filmstrip extends Component
/>
- ;
- })
- }
+ );
+ }
+ })}
diff --git a/app/lib/components/ParticipantList/ListPeer.jsx b/app/lib/components/ParticipantList/ListPeer.jsx
index d9f8861..abb56b7 100644
--- a/app/lib/components/ParticipantList/ListPeer.jsx
+++ b/app/lib/components/ParticipantList/ListPeer.jsx
@@ -63,8 +63,8 @@ const ListPeer = (props) =>
{
e.stopPropagation();
screenVisible ?
- roomClient.pausePeerScreen(peer.name) :
- roomClient.resumePeerScreen(peer.name);
+ roomClient.modifyPeerConsumer(peer.name, 'screen', true) :
+ roomClient.modifyPeerConsumer(peer.name, 'screen', false);
}}
/>
@@ -78,8 +78,8 @@ const ListPeer = (props) =>
{
e.stopPropagation();
micEnabled ?
- roomClient.mutePeerAudio(peer.name) :
- roomClient.unmutePeerAudio(peer.name);
+ roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
+ roomClient.modifyPeerConsumer(peer.name, 'mic', false);
}}
/>
diff --git a/app/lib/components/Peer.jsx b/app/lib/components/Peer.jsx
index f90e52d..214d374 100644
--- a/app/lib/components/Peer.jsx
+++ b/app/lib/components/Peer.jsx
@@ -121,8 +121,8 @@ class Peer extends Component
{
e.stopPropagation();
micEnabled ?
- roomClient.mutePeerAudio(peer.name) :
- roomClient.unmutePeerAudio(peer.name);
+ roomClient.modifyPeerConsumer(peer.name, 'mic', true) :
+ roomClient.modifyPeerConsumer(peer.name, 'mic', false);
}}
/>
diff --git a/app/lib/components/Peers.jsx b/app/lib/components/Peers.jsx
index dcdc747..840c371 100644
--- a/app/lib/components/Peers.jsx
+++ b/app/lib/components/Peers.jsx
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import debounce from 'lodash/debounce';
-import * as appPropTypes from './appPropTypes';
import { Appear } from './transitions';
import Peer from './Peer';
import HiddenPeers from './HiddenPeers';
@@ -105,33 +104,30 @@ class Peers extends React.Component
return (
+ { Object.keys(peers).map((peerName) =>
{
- peers.map((peer) =>
+ if (spotlights.find((spotlightsElement) => spotlightsElement === peerName))
{
-
spotlightsElement === peer.name)
- }
- >
-
+ return (
+
- ;
- })
- }
+ );
+ }
+ })}
{
- const peers = Object.values(state.peers);
const spotlights = state.room.spotlights;
const spotlightsLength = spotlights ? state.room.spotlights.length : 0;
const boxes = spotlightsLength + Object.values(state.consumers)
.filter((consumer) => consumer.source === 'screen').length;
return {
- peers,
+ peers : state.peers,
boxes,
activeSpeakerName : state.room.activeSpeakerName,
selectedPeerName : state.room.selectedPeerName,
diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx
index ca8d2b4..63ab009 100644
--- a/app/lib/components/Room.jsx
+++ b/app/lib/components/Room.jsx
@@ -20,9 +20,6 @@ import Draggable from 'react-draggable';
import { idle } from '../utils';
import Sidebar from './Sidebar';
import Filmstrip from './Filmstrip';
-import { configureDragDrop, HoldingOverlay } from './FileSharing/DragDropSharing';
-
-configureDragDrop();
// Hide toolbars after 10 seconds of inactivity.
const TIMEOUT = 10 * 1000;
@@ -78,8 +75,6 @@ class Room extends React.Component
return (
-
-
diff --git a/app/lib/components/ToolArea/ToolArea.jsx b/app/lib/components/ToolArea/ToolArea.jsx
index 5165796..dfbf2a3 100644
--- a/app/lib/components/ToolArea/ToolArea.jsx
+++ b/app/lib/components/ToolArea/ToolArea.jsx
@@ -6,7 +6,7 @@ import * as stateActions from '../../redux/stateActions';
import ParticipantList from '../ParticipantList/ParticipantList';
import Chat from '../Chat/Chat';
import Settings from '../Settings';
-import FileSharing from '../FileSharing';
+import FileSharing from '../FileSharing/FileSharing';
import TabHeader from './TabHeader';
class ToolArea extends React.Component
diff --git a/app/lib/components/ToolArea/ToolAreaButton.jsx b/app/lib/components/ToolArea/ToolAreaButton.jsx
deleted file mode 100644
index 20b0024..0000000
--- a/app/lib/components/ToolArea/ToolAreaButton.jsx
+++ /dev/null
@@ -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 (
-
-
toggleToolArea()}
- />
-
- {!toolAreaOpen && unread > 0 && (
- = 10 })}>
- {unread}
-
- )}
-
- );
- }
-}
-
-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;
diff --git a/app/lib/components/appPropTypes.js b/app/lib/components/appPropTypes.js
index 392e854..74b52e3 100644
--- a/app/lib/components/appPropTypes.js
+++ b/app/lib/components/appPropTypes.js
@@ -76,3 +76,16 @@ export const Message = PropTypes.shape(
text : 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
+ });
\ No newline at end of file
diff --git a/app/lib/redux/reducers/files.js b/app/lib/redux/reducers/files.js
new file mode 100644
index 0000000..19d5369
--- /dev/null
+++ b/app/lib/redux/reducers/files.js
@@ -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;
diff --git a/app/lib/redux/reducers/index.js b/app/lib/redux/reducers/index.js
index 1f59ace..fcafba6 100644
--- a/app/lib/redux/reducers/index.js
+++ b/app/lib/redux/reducers/index.js
@@ -8,7 +8,7 @@ import notifications from './notifications';
import chatmessages from './chatmessages';
import chatbehavior from './chatbehavior';
import toolarea from './toolarea';
-import sharing from './sharing';
+import files from './files';
const reducers = combineReducers(
{
@@ -21,7 +21,7 @@ const reducers = combineReducers(
chatmessages,
chatbehavior,
toolarea,
- sharing
+ files
});
export default reducers;
diff --git a/app/lib/redux/reducers/notifications.js b/app/lib/redux/reducers/notifications.js
index bf3e6c0..142308e 100644
--- a/app/lib/redux/reducers/notifications.js
+++ b/app/lib/redux/reducers/notifications.js
@@ -1,6 +1,4 @@
-const initialState = [];
-
-const notifications = (state = initialState, action) =>
+const notifications = (state = [], action) =>
{
switch (action.type)
{
diff --git a/app/lib/redux/reducers/room.js b/app/lib/redux/reducers/room.js
index 89d2b21..eed2e8a 100644
--- a/app/lib/redux/reducers/room.js
+++ b/app/lib/redux/reducers/room.js
@@ -3,6 +3,7 @@ const initialState =
url : null,
state : 'new', // new/connecting/connected/disconnected/closed,
activeSpeakerName : null,
+ torrentSupport : false,
showSettings : false,
advancedMode : false,
fullScreenConsumer : null, // ConsumerID
@@ -41,6 +42,13 @@ const room = (state = initialState, action) =>
return { ...state, activeSpeakerName: peerName };
}
+ case 'FILE_SHARING_SUPPORTED':
+ {
+ const { supported } = action.payload;
+
+ return { ...state, torrentSupport: supported };
+ }
+
case 'TOGGLE_SETTINGS':
{
const showSettings = !state.showSettings;
diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js
index 3ced6b6..a837631 100644
--- a/app/lib/redux/stateActions.js
+++ b/app/lib/redux/stateActions.js
@@ -70,6 +70,14 @@ export const setWebcamDevices = (devices) =>
};
};
+export const setFileSharingSupported = (supported) =>
+{
+ return {
+ type : 'FILE_SHARING_SUPPORTED',
+ payload : { supported }
+ };
+};
+
export const setDisplayName = (displayName) =>
{
return {
@@ -455,11 +463,11 @@ export const dropMessages = () =>
};
};
-export const addFile = (payload) =>
+export const addFile = (file) =>
{
return {
- type : 'ADD_FILE',
- payload
+ type : 'ADD_FILE',
+ 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) =>
({
type : 'SET_PICTURE',
diff --git a/app/stylus/components/ToolArea.styl b/app/stylus/components/ToolArea.styl
index 9283d42..038ca8d 100644
--- a/app/stylus/components/ToolArea.styl
+++ b/app/stylus/components/ToolArea.styl
@@ -19,12 +19,6 @@
}
}
-[data-component='ToolAreaButton'] {
- &.on {
- right: 80%;
- }
-}
-
[data-component='ToolArea'] {
&.open {
width: 80%;
@@ -153,12 +147,6 @@
}
@media (min-width: 600px) {
- [data-component='ToolAreaButton'] {
- &.on {
- right: 60%;
- }
- }
-
[data-component='ToolArea'] {
&.open {
width: 60%;
@@ -167,12 +155,6 @@
}
@media (min-width: 900px) {
- [data-component='ToolAreaButton'] {
- &.on {
- right: 40%;
- }
- }
-
[data-component='ToolArea'] {
&.open {
width: 40%;
@@ -181,12 +163,6 @@
}
@media (min-width: 1500px) {
- [data-component='ToolAreaButton'] {
- &.on {
- right: 25%;
- }
- }
-
[data-component='ToolArea'] {
&.open {
width: 25%;
@@ -194,79 +170,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'] {
width: 100%;
height: 100%;