Major cleanup

master
Håvar Aambø Fosstveit 2018-12-12 22:28:33 +01:00
parent fa91d9b9e2
commit 60fb9c735e
32 changed files with 506 additions and 16543 deletions

View File

@ -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"
], ],

View File

@ -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:
{ {

View File

@ -30,10 +30,21 @@ const VIDEO_CONSTRAINS =
aspectRatio : 1.334 aspectRatio : 1.334
}; };
let store;
export default class RoomClient export default class RoomClient
{ {
/**
* @param {Object} data
* @param {Object} data.store - The Redux store.
*/
static init(data)
{
store = data.store;
}
constructor( constructor(
{ roomId, peerName, displayName, device, useSimulcast, produce, dispatch, getState }) { roomId, peerName, displayName, device, useSimulcast, produce })
{ {
logger.debug( logger.debug(
'constructor() [roomId:"%s", peerName:"%s", displayName:"%s", device:%s]', 'constructor() [roomId:"%s", peerName:"%s", displayName:"%s", device:%s]',
@ -53,12 +64,6 @@ export default class RoomClient
// Whether simulcast should be used. // Whether simulcast should be used.
this._useSimulcast = useSimulcast; this._useSimulcast = useSimulcast;
// Redux store dispatch function.
this._dispatch = dispatch;
// Redux store getState function.
this._getState = getState;
// This device // This device
this._device = device; this._device = device;
@ -139,7 +144,7 @@ export default class RoomClient
// the 'leaveRoom' notification). // the 'leaveRoom' notification).
setTimeout(() => this._signalingSocket.close(), 250); setTimeout(() => this._signalingSocket.close(), 250);
this._dispatch(stateActions.setRoomState('closed')); store.dispatch(stateActions.setRoomState('closed'));
} }
_startKeyListener() _startKeyListener()
@ -161,21 +166,21 @@ export default class RoomClient
{ {
case 'a': // Activate advanced mode case 'a': // Activate advanced mode
{ {
this._dispatch(stateActions.toggleAdvancedMode()); store.dispatch(stateActions.toggleAdvancedMode());
this.notify('Toggled advanced mode.'); this.notify('Toggled advanced mode.');
break; break;
} }
case '1': // Set democratic view case '1': // Set democratic view
{ {
this._dispatch(stateActions.setDisplayMode('democratic')); store.dispatch(stateActions.setDisplayMode('democratic'));
this.notify('Changed layout to democratic view.'); this.notify('Changed layout to democratic view.');
break; break;
} }
case '2': // Set filmstrip view case '2': // Set filmstrip view
{ {
this._dispatch(stateActions.setDisplayMode('filmstrip')); store.dispatch(stateActions.setDisplayMode('filmstrip'));
this.notify('Changed layout to filmstrip view.'); this.notify('Changed layout to filmstrip view.');
break; break;
} }
@ -225,7 +230,7 @@ export default class RoomClient
notify(text) notify(text)
{ {
this._dispatch(requestActions.notify({ text: text })); store.dispatch(requestActions.notify({ text: text }));
} }
timeoutCallback(callback) timeoutCallback(callback)
@ -290,7 +295,7 @@ export default class RoomClient
{ {
await this.sendRequest('change-display-name', { displayName }); await this.sendRequest('change-display-name', { displayName });
this._dispatch(stateActions.setDisplayName(displayName)); store.dispatch(stateActions.setDisplayName(displayName));
this.notify(`Your display name changed to ${displayName}.`); this.notify(`Your display name changed to ${displayName}.`);
} }
@ -302,7 +307,7 @@ export default class RoomClient
// We need to refresh the component for it to render the previous // We need to refresh the component for it to render the previous
// displayName again. // displayName again.
this._dispatch(stateActions.setDisplayName()); store.dispatch(stateActions.setDisplayName());
} }
} }
@ -326,6 +331,9 @@ export default class RoomClient
try try
{ {
store.dispatch(
stateActions.addUserMessage(chatMessage.text));
await this.sendRequest('chat-message', { chatMessage }); await this.sendRequest('chat-message', { chatMessage });
} }
catch (error) catch (error)
@ -367,7 +375,7 @@ export default class RoomClient
if (chatHistory.length > 0) if (chatHistory.length > 0)
{ {
logger.debug('Got chat history'); logger.debug('Got chat history');
this._dispatch( store.dispatch(
stateActions.addChatHistory(chatHistory)); stateActions.addChatHistory(chatHistory));
} }
@ -375,7 +383,7 @@ export default class RoomClient
{ {
logger.debug('Got files history'); logger.debug('Got files history');
this._dispatch(stateActions.addFileHistory(fileHistory)); store.dispatch(stateActions.addFileHistory(fileHistory));
} }
if (lastN.length > 0) if (lastN.length > 0)
@ -466,7 +474,7 @@ export default class RoomClient
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
{ {
window.addEventListener('message', _onExtensionMessage, false); window.addEventListener('message', _onExtensionMessage, false);
// eslint-disable-next-line no-undef // eslint-disable-next-line
chrome.webstore.install(null, _successfulInstall, _failedInstall); chrome.webstore.install(null, _successfulInstall, _failedInstall);
function _onExtensionMessage({ data }) function _onExtensionMessage({ data })
{ {
@ -494,7 +502,7 @@ export default class RoomClient
.then(() => .then(() =>
{ {
// This should be handled better // This should be handled better
this._dispatch(stateActions.setScreenCapabilities( store.dispatch(stateActions.setScreenCapabilities(
{ {
canShareScreen : this._room.canSend('video'), canShareScreen : this._room.canSend('video'),
needExtension : false needExtension : false
@ -510,7 +518,7 @@ export default class RoomClient
{ {
logger.debug('enableScreenSharing()'); logger.debug('enableScreenSharing()');
this._dispatch(stateActions.setScreenShareInProgress(true)); store.dispatch(stateActions.setScreenShareInProgress(true));
try try
{ {
@ -521,7 +529,7 @@ export default class RoomClient
logger.error('enableScreenSharing() | failed: %o', error); logger.error('enableScreenSharing() | failed: %o', error);
} }
this._dispatch(stateActions.setScreenShareInProgress(false)); store.dispatch(stateActions.setScreenShareInProgress(false));
} }
async enableWebcam() async enableWebcam()
@ -531,7 +539,7 @@ export default class RoomClient
// Store in cookie. // Store in cookie.
cookiesManager.setDevices({ webcamEnabled: true }); cookiesManager.setDevices({ webcamEnabled: true });
this._dispatch(stateActions.setWebcamInProgress(true)); store.dispatch(stateActions.setWebcamInProgress(true));
try try
{ {
@ -542,14 +550,14 @@ export default class RoomClient
logger.error('enableWebcam() | failed: %o', error); logger.error('enableWebcam() | failed: %o', error);
} }
this._dispatch(stateActions.setWebcamInProgress(false)); store.dispatch(stateActions.setWebcamInProgress(false));
} }
async disableScreenSharing() async disableScreenSharing()
{ {
logger.debug('disableScreenSharing()'); logger.debug('disableScreenSharing()');
this._dispatch(stateActions.setScreenShareInProgress(true)); store.dispatch(stateActions.setScreenShareInProgress(true));
try try
{ {
@ -560,7 +568,7 @@ export default class RoomClient
logger.error('disableScreenSharing() | failed: %o', error); logger.error('disableScreenSharing() | failed: %o', error);
} }
this._dispatch(stateActions.setScreenShareInProgress(false)); store.dispatch(stateActions.setScreenShareInProgress(false));
} }
async disableWebcam() async disableWebcam()
@ -570,7 +578,7 @@ export default class RoomClient
// Store in cookie. // Store in cookie.
cookiesManager.setDevices({ webcamEnabled: false }); cookiesManager.setDevices({ webcamEnabled: false });
this._dispatch(stateActions.setWebcamInProgress(true)); store.dispatch(stateActions.setWebcamInProgress(true));
try try
{ {
@ -581,14 +589,14 @@ export default class RoomClient
logger.error('disableWebcam() | failed: %o', error); logger.error('disableWebcam() | failed: %o', error);
} }
this._dispatch(stateActions.setWebcamInProgress(false)); store.dispatch(stateActions.setWebcamInProgress(false));
} }
async changeAudioDevice(deviceId) async changeAudioDevice(deviceId)
{ {
logger.debug('changeAudioDevice() [deviceId: %s]', deviceId); logger.debug('changeAudioDevice() [deviceId: %s]', deviceId);
this._dispatch( store.dispatch(
stateActions.setAudioInProgress(true)); stateActions.setAudioInProgress(true));
try try
@ -642,13 +650,13 @@ export default class RoomClient
if (volume !== this._micProducer.volume) if (volume !== this._micProducer.volume)
{ {
this._micProducer.volume = volume; this._micProducer.volume = volume;
this._dispatch(stateActions.setProducerVolume(this._micProducer.id, volume)); store.dispatch(stateActions.setProducerVolume(this._micProducer.id, volume));
} }
}); });
track.stop(); track.stop();
this._dispatch( store.dispatch(
stateActions.setProducerTrack(this._micProducer.id, newTrack)); stateActions.setProducerTrack(this._micProducer.id, newTrack));
cookiesManager.setAudioDevice({ audioDeviceId: deviceId }); cookiesManager.setAudioDevice({ audioDeviceId: deviceId });
@ -660,7 +668,7 @@ export default class RoomClient
logger.error('changeAudioDevice() failed: %o', error); logger.error('changeAudioDevice() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setAudioInProgress(false)); stateActions.setAudioInProgress(false));
} }
@ -668,7 +676,7 @@ export default class RoomClient
{ {
logger.debug('changeWebcam() [deviceId: %s]', deviceId); logger.debug('changeWebcam() [deviceId: %s]', deviceId);
this._dispatch( store.dispatch(
stateActions.setWebcamInProgress(true)); stateActions.setWebcamInProgress(true));
try try
@ -704,7 +712,7 @@ export default class RoomClient
track.stop(); track.stop();
this._dispatch( store.dispatch(
stateActions.setProducerTrack(this._webcamProducer.id, newTrack)); stateActions.setProducerTrack(this._webcamProducer.id, newTrack));
cookiesManager.setVideoDevice({ videoDeviceId: deviceId }); cookiesManager.setVideoDevice({ videoDeviceId: deviceId });
@ -716,7 +724,7 @@ export default class RoomClient
logger.error('changeWebcam() failed: %o', error); logger.error('changeWebcam() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setWebcamInProgress(false)); stateActions.setWebcamInProgress(false));
} }
@ -727,7 +735,7 @@ export default class RoomClient
let oldResolution; let oldResolution;
let newResolution; let newResolution;
this._dispatch( store.dispatch(
stateActions.setWebcamInProgress(true)); stateActions.setWebcamInProgress(true));
try try
@ -768,7 +776,7 @@ export default class RoomClient
track.stop(); track.stop();
this._dispatch( store.dispatch(
stateActions.setProducerTrack(this._webcamProducer.id, newTrack)); stateActions.setProducerTrack(this._webcamProducer.id, newTrack));
} }
catch (error) catch (error)
@ -778,7 +786,7 @@ export default class RoomClient
this._webcam.resolution = oldResolution; this._webcam.resolution = oldResolution;
} }
this._dispatch( store.dispatch(
stateActions.setWebcamInProgress(false)); stateActions.setWebcamInProgress(false));
} }
@ -788,7 +796,7 @@ export default class RoomClient
this._spotlights.setPeerSpotlight(peerName); this._spotlights.setPeerSpotlight(peerName);
this._dispatch( store.dispatch(
stateActions.setSelectedPeer(peerName)); stateActions.setSelectedPeer(peerName));
} }
@ -796,7 +804,7 @@ export default class RoomClient
{ {
logger.debug('mutePeerAudio() [peerName:"%s"]', peerName); logger.debug('mutePeerAudio() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerAudioInProgress(peerName, true)); stateActions.setPeerAudioInProgress(peerName, true));
try try
@ -820,7 +828,7 @@ export default class RoomClient
logger.error('mutePeerAudio() failed: %o', error); logger.error('mutePeerAudio() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerAudioInProgress(peerName, false)); stateActions.setPeerAudioInProgress(peerName, false));
} }
@ -828,7 +836,7 @@ export default class RoomClient
{ {
logger.debug('unmutePeerAudio() [peerName:"%s"]', peerName); logger.debug('unmutePeerAudio() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerAudioInProgress(peerName, true)); stateActions.setPeerAudioInProgress(peerName, true));
try try
@ -852,7 +860,7 @@ export default class RoomClient
logger.error('unmutePeerAudio() failed: %o', error); logger.error('unmutePeerAudio() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerAudioInProgress(peerName, false)); stateActions.setPeerAudioInProgress(peerName, false));
} }
@ -860,7 +868,7 @@ export default class RoomClient
{ {
logger.debug('pausePeerVideo() [peerName:"%s"]', peerName); logger.debug('pausePeerVideo() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerVideoInProgress(peerName, true)); stateActions.setPeerVideoInProgress(peerName, true));
try try
@ -884,7 +892,7 @@ export default class RoomClient
logger.error('pausePeerVideo() failed: %o', error); logger.error('pausePeerVideo() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerVideoInProgress(peerName, false)); stateActions.setPeerVideoInProgress(peerName, false));
} }
@ -892,7 +900,7 @@ export default class RoomClient
{ {
logger.debug('resumePeerVideo() [peerName:"%s"]', peerName); logger.debug('resumePeerVideo() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerVideoInProgress(peerName, true)); stateActions.setPeerVideoInProgress(peerName, true));
try try
@ -916,7 +924,7 @@ export default class RoomClient
logger.error('resumePeerVideo() failed: %o', error); logger.error('resumePeerVideo() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerVideoInProgress(peerName, false)); stateActions.setPeerVideoInProgress(peerName, false));
} }
@ -924,7 +932,7 @@ export default class RoomClient
{ {
logger.debug('pausePeerScreen() [peerName:"%s"]', peerName); logger.debug('pausePeerScreen() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerScreenInProgress(peerName, true)); stateActions.setPeerScreenInProgress(peerName, true));
try try
@ -948,7 +956,7 @@ export default class RoomClient
logger.error('pausePeerScreen() failed: %o', error); logger.error('pausePeerScreen() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerScreenInProgress(peerName, false)); stateActions.setPeerScreenInProgress(peerName, false));
} }
@ -956,7 +964,7 @@ export default class RoomClient
{ {
logger.debug('resumePeerScreen() [peerName:"%s"]', peerName); logger.debug('resumePeerScreen() [peerName:"%s"]', peerName);
this._dispatch( store.dispatch(
stateActions.setPeerScreenInProgress(peerName, true)); stateActions.setPeerScreenInProgress(peerName, true));
try try
@ -980,7 +988,7 @@ export default class RoomClient
logger.error('resumePeerScreen() failed: %o', error); logger.error('resumePeerScreen() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setPeerScreenInProgress(peerName, false)); stateActions.setPeerScreenInProgress(peerName, false));
} }
@ -988,7 +996,7 @@ export default class RoomClient
{ {
logger.debug('enableAudioOnly()'); logger.debug('enableAudioOnly()');
this._dispatch( store.dispatch(
stateActions.setAudioOnlyInProgress(true)); stateActions.setAudioOnlyInProgress(true));
try try
@ -1007,7 +1015,7 @@ export default class RoomClient
} }
} }
this._dispatch( store.dispatch(
stateActions.setAudioOnlyState(true)); stateActions.setAudioOnlyState(true));
} }
catch (error) catch (error)
@ -1015,7 +1023,7 @@ export default class RoomClient
logger.error('enableAudioOnly() failed: %o', error); logger.error('enableAudioOnly() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setAudioOnlyInProgress(false)); stateActions.setAudioOnlyInProgress(false));
} }
@ -1023,7 +1031,7 @@ export default class RoomClient
{ {
logger.debug('disableAudioOnly()'); logger.debug('disableAudioOnly()');
this._dispatch( store.dispatch(
stateActions.setAudioOnlyInProgress(true)); stateActions.setAudioOnlyInProgress(true));
try try
@ -1042,7 +1050,7 @@ export default class RoomClient
} }
} }
this._dispatch( store.dispatch(
stateActions.setAudioOnlyState(false)); stateActions.setAudioOnlyState(false));
} }
catch (error) catch (error)
@ -1050,7 +1058,7 @@ export default class RoomClient
logger.error('disableAudioOnly() failed: %o', error); logger.error('disableAudioOnly() failed: %o', error);
} }
this._dispatch( store.dispatch(
stateActions.setAudioOnlyInProgress(false)); stateActions.setAudioOnlyInProgress(false));
} }
@ -1058,14 +1066,14 @@ export default class RoomClient
{ {
logger.debug('sendRaiseHandState: ', state); logger.debug('sendRaiseHandState: ', state);
this._dispatch( store.dispatch(
stateActions.setMyRaiseHandStateInProgress(true)); stateActions.setMyRaiseHandStateInProgress(true));
try try
{ {
await this.sendRequest('raisehand-message', { raiseHandState: state }); await this.sendRequest('raisehand-message', { raiseHandState: state });
this._dispatch( store.dispatch(
stateActions.setMyRaiseHandState(state)); stateActions.setMyRaiseHandState(state));
} }
catch (error) catch (error)
@ -1075,10 +1083,10 @@ export default class RoomClient
this.notify(`An error occured while ${state ? 'raising' : 'lowering'} hand.`); this.notify(`An error occured while ${state ? 'raising' : 'lowering'} hand.`);
// We need to refresh the component for it to render changed state // We need to refresh the component for it to render changed state
this._dispatch(stateActions.setMyRaiseHandState(!state)); store.dispatch(stateActions.setMyRaiseHandState(!state));
} }
this._dispatch( store.dispatch(
stateActions.setMyRaiseHandStateInProgress(false)); stateActions.setMyRaiseHandStateInProgress(false));
} }
@ -1086,7 +1094,7 @@ export default class RoomClient
{ {
logger.debug('restartIce()'); logger.debug('restartIce()');
this._dispatch( store.dispatch(
stateActions.setRestartIceInProgress(true)); stateActions.setRestartIceInProgress(true));
try try
@ -1101,14 +1109,14 @@ export default class RoomClient
// Make it artificially longer. // Make it artificially longer.
setTimeout(() => setTimeout(() =>
{ {
this._dispatch( store.dispatch(
stateActions.setRestartIceInProgress(false)); stateActions.setRestartIceInProgress(false));
}, 500); }, 500);
} }
_join({ displayName, device }) _join({ displayName, device })
{ {
this._dispatch(stateActions.setRoomState('connecting')); store.dispatch(stateActions.setRoomState('connecting'));
this._signalingSocket.on('connect', () => this._signalingSocket.on('connect', () =>
{ {
@ -1127,7 +1135,7 @@ export default class RoomClient
try { this._room.remoteClose({ cause: 'signaling disconnected' }); } try { this._room.remoteClose({ cause: 'signaling disconnected' }); }
catch (error) {} catch (error) {}
this._dispatch(stateActions.setRoomState('connecting')); store.dispatch(stateActions.setRoomState('connecting'));
}); });
this._signalingSocket.on('close', () => this._signalingSocket.on('close', () =>
@ -1151,7 +1159,7 @@ export default class RoomClient
{ {
const { peerName } = data; const { peerName } = data;
this._dispatch( store.dispatch(
stateActions.setRoomActiveSpeaker(peerName)); stateActions.setRoomActiveSpeaker(peerName));
if (peerName && peerName !== this._peerName) if (peerName && peerName !== this._peerName)
@ -1177,7 +1185,7 @@ export default class RoomClient
peer.appData.displayName = displayName; peer.appData.displayName = displayName;
this._dispatch( store.dispatch(
stateActions.setPeerDisplayName(displayName, peerName)); stateActions.setPeerDisplayName(displayName, peerName));
this.notify(`${oldDisplayName} changed their display name to ${displayName}.`); this.notify(`${oldDisplayName} changed their display name to ${displayName}.`);
@ -1187,7 +1195,7 @@ export default class RoomClient
{ {
const { peerName, picture } = data; const { peerName, picture } = data;
this._dispatch(stateActions.setPeerPicture(peerName, picture)); store.dispatch(stateActions.setPeerPicture(peerName, picture));
}); });
// This means: server wants to change MY user information // This means: server wants to change MY user information
@ -1198,8 +1206,8 @@ export default class RoomClient
this.changeDisplayName(data.name); this.changeDisplayName(data.name);
this.changeProfilePicture(data.picture); this.changeProfilePicture(data.picture);
this._dispatch(stateActions.setPicture(data.picture)); store.dispatch(stateActions.setPicture(data.picture));
this._dispatch(stateActions.loggedIn()); store.dispatch(stateActions.loggedIn());
this.notify('You are logged in.'); this.notify('You are logged in.');
@ -1224,7 +1232,7 @@ export default class RoomClient
this.notify(`${peer.appData.displayName} ${raiseHandState ? 'raised' : 'lowered'} their hand.`); this.notify(`${peer.appData.displayName} ${raiseHandState ? 'raised' : 'lowered'} their hand.`);
this._dispatch( store.dispatch(
stateActions.setPeerRaiseHandState(peerName, raiseHandState)); stateActions.setPeerRaiseHandState(peerName, raiseHandState));
}); });
@ -1234,12 +1242,12 @@ export default class RoomClient
logger.debug('Got chat from "%s"', peerName); logger.debug('Got chat from "%s"', peerName);
this._dispatch( store.dispatch(
stateActions.addResponseMessage({ ...chatMessage, peerName })); stateActions.addResponseMessage({ ...chatMessage, peerName }));
if (!this._getState().toolarea.toolAreaOpen || if (!store.getState().toolarea.toolAreaOpen ||
(this._getState().toolarea.toolAreaOpen && (store.getState().toolarea.toolAreaOpen &&
this._getState().toolarea.currentToolTab !== 'chat')) // Make sound store.getState().toolarea.currentToolTab !== 'chat')) // Make sound
{ {
this._soundNotification(); this._soundNotification();
} }
@ -1259,13 +1267,13 @@ export default class RoomClient
return; return;
} }
this._dispatch(stateActions.addFile(file)); store.dispatch(stateActions.addFile(file));
this.notify(`${peer.appData.displayName} shared a file.`); this.notify(`${peer.appData.displayName} shared a file.`);
if (!this._getState().toolarea.toolAreaOpen || if (!store.getState().toolarea.toolAreaOpen ||
(this._getState().toolarea.toolAreaOpen && (store.getState().toolarea.toolAreaOpen &&
this._getState().toolarea.currentToolTab !== 'files')) // Make sound store.getState().toolarea.currentToolTab !== 'files')) // Make sound
{ {
this._soundNotification(); this._soundNotification();
} }
@ -1287,7 +1295,7 @@ export default class RoomClient
{ {
logger.warn('mediasoup Peer/Room remotely closed [appData:%o]', appData); logger.warn('mediasoup Peer/Room remotely closed [appData:%o]', appData);
this._dispatch(stateActions.setRoomState('closed')); store.dispatch(stateActions.setRoomState('closed'));
return; return;
} }
@ -1350,12 +1358,12 @@ export default class RoomClient
}); });
// Set our media capabilities. // Set our media capabilities.
this._dispatch(stateActions.setMediaCapabilities( store.dispatch(stateActions.setMediaCapabilities(
{ {
canSendMic : this._room.canSend('audio'), canSendMic : this._room.canSend('audio'),
canSendWebcam : this._room.canSend('video') canSendWebcam : this._room.canSend('video')
})); }));
this._dispatch(stateActions.setScreenCapabilities( store.dispatch(stateActions.setScreenCapabilities(
{ {
canShareScreen : this._room.canSend('video') && canShareScreen : this._room.canSend('video') &&
this._screenSharing.isScreenShareAvailable(), this._screenSharing.isScreenShareAvailable(),
@ -1378,10 +1386,10 @@ export default class RoomClient
} }
} }
this._dispatch(stateActions.setRoomState('connected')); store.dispatch(stateActions.setRoomState('connected'));
// Clean all the existing notifcations. // Clean all the existing notifcations.
this._dispatch(stateActions.removeAllNotifications()); store.dispatch(stateActions.removeAllNotifications());
this.getServerHistory(); this.getServerHistory();
@ -1389,7 +1397,7 @@ export default class RoomClient
this._spotlights.on('spotlights-updated', (spotlights) => this._spotlights.on('spotlights-updated', (spotlights) =>
{ {
this._dispatch(stateActions.setSpotlights(spotlights)); store.dispatch(stateActions.setSpotlights(spotlights));
this.updateSpotlights(spotlights); this.updateSpotlights(spotlights);
}); });
@ -1440,7 +1448,7 @@ export default class RoomClient
this._micProducer = producer; this._micProducer = producer;
this._dispatch(stateActions.addProducer( store.dispatch(stateActions.addProducer(
{ {
id : producer.id, id : producer.id,
source : 'mic', source : 'mic',
@ -1460,7 +1468,7 @@ export default class RoomClient
'mic Producer "close" event [originator:%s]', originator); 'mic Producer "close" event [originator:%s]', originator);
this._micProducer = null; this._micProducer = null;
this._dispatch(stateActions.removeProducer(producer.id)); store.dispatch(stateActions.removeProducer(producer.id));
}); });
producer.on('pause', (originator) => producer.on('pause', (originator) =>
@ -1468,7 +1476,7 @@ export default class RoomClient
logger.debug( logger.debug(
'mic Producer "pause" event [originator:%s]', originator); 'mic Producer "pause" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerPaused(producer.id, originator)); store.dispatch(stateActions.setProducerPaused(producer.id, originator));
}); });
producer.on('resume', (originator) => producer.on('resume', (originator) =>
@ -1476,7 +1484,7 @@ export default class RoomClient
logger.debug( logger.debug(
'mic Producer "resume" event [originator:%s]', originator); 'mic Producer "resume" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerResumed(producer.id, originator)); store.dispatch(stateActions.setProducerResumed(producer.id, originator));
}); });
producer.on('handled', () => producer.on('handled', () =>
@ -1512,7 +1520,7 @@ export default class RoomClient
if (volume !== producer.volume) if (volume !== producer.volume)
{ {
producer.volume = volume; producer.volume = volume;
this._dispatch(stateActions.setProducerVolume(producer.id, volume)); store.dispatch(stateActions.setProducerVolume(producer.id, volume));
} }
}); });
} }
@ -1565,7 +1573,7 @@ export default class RoomClient
this._screenSharingProducer = producer; this._screenSharingProducer = producer;
this._dispatch(stateActions.addProducer( store.dispatch(stateActions.addProducer(
{ {
id : producer.id, id : producer.id,
source : 'screen', source : 'screen',
@ -1583,7 +1591,7 @@ export default class RoomClient
'webcam Producer "close" event [originator:%s]', originator); 'webcam Producer "close" event [originator:%s]', originator);
this._screenSharingProducer = null; this._screenSharingProducer = null;
this._dispatch(stateActions.removeProducer(producer.id)); store.dispatch(stateActions.removeProducer(producer.id));
}); });
producer.on('trackended', (originator) => producer.on('trackended', (originator) =>
@ -1599,7 +1607,7 @@ export default class RoomClient
logger.debug( logger.debug(
'webcam Producer "pause" event [originator:%s]', originator); 'webcam Producer "pause" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerPaused(producer.id, originator)); store.dispatch(stateActions.setProducerPaused(producer.id, originator));
}); });
producer.on('resume', (originator) => producer.on('resume', (originator) =>
@ -1607,7 +1615,7 @@ export default class RoomClient
logger.debug( logger.debug(
'webcam Producer "resume" event [originator:%s]', originator); 'webcam Producer "resume" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerResumed(producer.id, originator)); store.dispatch(stateActions.setProducerResumed(producer.id, originator));
}); });
producer.on('handled', () => producer.on('handled', () =>
@ -1677,7 +1685,7 @@ export default class RoomClient
this._webcamProducer = producer; this._webcamProducer = producer;
this._dispatch(stateActions.addProducer( store.dispatch(stateActions.addProducer(
{ {
id : producer.id, id : producer.id,
source : 'webcam', source : 'webcam',
@ -1696,7 +1704,7 @@ export default class RoomClient
'webcam Producer "close" event [originator:%s]', originator); 'webcam Producer "close" event [originator:%s]', originator);
this._webcamProducer = null; this._webcamProducer = null;
this._dispatch(stateActions.removeProducer(producer.id)); store.dispatch(stateActions.removeProducer(producer.id));
}); });
producer.on('pause', (originator) => producer.on('pause', (originator) =>
@ -1704,7 +1712,7 @@ export default class RoomClient
logger.debug( logger.debug(
'webcam Producer "pause" event [originator:%s]', originator); 'webcam Producer "pause" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerPaused(producer.id, originator)); store.dispatch(stateActions.setProducerPaused(producer.id, originator));
}); });
producer.on('resume', (originator) => producer.on('resume', (originator) =>
@ -1712,7 +1720,7 @@ export default class RoomClient
logger.debug( logger.debug(
'webcam Producer "resume" event [originator:%s]', originator); 'webcam Producer "resume" event [originator:%s]', originator);
this._dispatch(stateActions.setProducerResumed(producer.id, originator)); store.dispatch(stateActions.setProducerResumed(producer.id, originator));
}); });
producer.on('handled', () => producer.on('handled', () =>
@ -1775,10 +1783,10 @@ export default class RoomClient
else if (!this._audioDevices.has(currentAudioDeviceId)) else if (!this._audioDevices.has(currentAudioDeviceId))
this._audioDevice.device = array[0]; this._audioDevice.device = array[0];
this._dispatch( store.dispatch(
stateActions.setCanChangeAudioDevice(len >= 2)); stateActions.setCanChangeAudioDevice(len >= 2));
if (len >= 1) if (len >= 1)
this._dispatch( store.dispatch(
stateActions.setAudioDevices(this._audioDevices)); stateActions.setAudioDevices(this._audioDevices));
} }
catch (error) catch (error)
@ -1823,7 +1831,7 @@ export default class RoomClient
this._webcam.device = array[0]; this._webcam.device = array[0];
if (len >= 1) if (len >= 1)
this._dispatch( store.dispatch(
stateActions.setWebcamDevices(this._webcams)); stateActions.setWebcamDevices(this._webcams));
} }
catch (error) catch (error)
@ -1836,7 +1844,7 @@ export default class RoomClient
{ {
const displayName = peer.appData.displayName; const displayName = peer.appData.displayName;
this._dispatch(stateActions.addPeer( store.dispatch(stateActions.addPeer(
{ {
name : peer.name, name : peer.name,
displayName : displayName, displayName : displayName,
@ -1861,7 +1869,7 @@ export default class RoomClient
'peer "close" event [name:"%s", originator:%s]', 'peer "close" event [name:"%s", originator:%s]',
peer.name, originator); peer.name, originator);
this._dispatch(stateActions.removePeer(peer.name)); store.dispatch(stateActions.removePeer(peer.name));
if (this._room.joined) if (this._room.joined)
{ {
@ -1883,7 +1891,7 @@ export default class RoomClient
{ {
const codec = consumer.rtpParameters.codecs[0]; const codec = consumer.rtpParameters.codecs[0];
this._dispatch(stateActions.addConsumer( store.dispatch(stateActions.addConsumer(
{ {
id : consumer.id, id : consumer.id,
peerName : consumer.peer.name, peerName : consumer.peer.name,
@ -1903,7 +1911,7 @@ export default class RoomClient
'consumer "close" event [id:%s, originator:%s, consumer:%o]', 'consumer "close" event [id:%s, originator:%s, consumer:%o]',
consumer.id, originator, consumer); consumer.id, originator, consumer);
this._dispatch(stateActions.removeConsumer( store.dispatch(stateActions.removeConsumer(
consumer.id, consumer.peer.name)); consumer.id, consumer.peer.name));
}); });
@ -1938,7 +1946,7 @@ export default class RoomClient
if (volume !== consumer.volume) if (volume !== consumer.volume)
{ {
consumer.volume = volume; consumer.volume = volume;
this._dispatch(stateActions.setConsumerVolume(consumer.id, volume)); store.dispatch(stateActions.setConsumerVolume(consumer.id, volume));
} }
}); });
} }
@ -1950,7 +1958,7 @@ export default class RoomClient
'consumer "pause" event [id:%s, originator:%s, consumer:%o]', 'consumer "pause" event [id:%s, originator:%s, consumer:%o]',
consumer.id, originator, consumer); consumer.id, originator, consumer);
this._dispatch(stateActions.setConsumerPaused(consumer.id, originator)); store.dispatch(stateActions.setConsumerPaused(consumer.id, originator));
}); });
consumer.on('resume', (originator) => consumer.on('resume', (originator) =>
@ -1959,7 +1967,7 @@ export default class RoomClient
'consumer "resume" event [id:%s, originator:%s, consumer:%o]', 'consumer "resume" event [id:%s, originator:%s, consumer:%o]',
consumer.id, originator, consumer); consumer.id, originator, consumer);
this._dispatch(stateActions.setConsumerResumed(consumer.id, originator)); store.dispatch(stateActions.setConsumerResumed(consumer.id, originator));
}); });
consumer.on('effectiveprofilechange', (profile) => consumer.on('effectiveprofilechange', (profile) =>
@ -1968,7 +1976,7 @@ export default class RoomClient
'consumer "effectiveprofilechange" event [id:%s, consumer:%o, profile:%s]', 'consumer "effectiveprofilechange" event [id:%s, consumer:%o, profile:%s]',
consumer.id, consumer, profile); consumer.id, consumer, profile);
this._dispatch(stateActions.setConsumerEffectiveProfile(consumer.id, profile)); store.dispatch(stateActions.setConsumerEffectiveProfile(consumer.id, profile));
}); });
// Receive the consumer (if we can). // Receive the consumer (if we can).
@ -1985,7 +1993,7 @@ export default class RoomClient
consumer.receive(this._recvTransport) consumer.receive(this._recvTransport)
.then((track) => .then((track) =>
{ {
this._dispatch(stateActions.setConsumerTrack(consumer.id, track)); store.dispatch(stateActions.setConsumerTrack(consumer.id, track));
}) })
.catch((error) => .catch((error) =>
{ {

View File

@ -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>
);
}

View File

@ -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;

View File

@ -30,41 +30,47 @@ class MessageList extends Component
return ( return (
<div data-component='MessageList' id='messages'> <div data-component='MessageList' id='messages'>
{ chatmessages.length > 0 ? <Choose>
chatmessages.map((message, i) => <When condition={chatmessages.length > 0}>
{ {
const messageTime = new Date(message.time); chatmessages.map((message, i) =>
{
const messageTime = new Date(message.time);
const picture = (message.sender === 'response' ? const picture = (message.sender === 'response' ?
message.picture : this.props.myPicture) || 'resources/images/avatar-empty.jpeg'; message.picture : this.props.myPicture) || 'resources/images/avatar-empty.jpeg';
return ( return (
<div className='message' key={i}> <div className='message' key={i}>
<div className={message.sender}> <div className={message.sender}>
<img className='message-avatar' src={picture} /> <img className='message-avatar' src={picture} />
<div className='message-content'> <div className='message-content'>
<div <div
className='message-text' className='message-text'
// eslint-disable-next-line react/no-danger // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html : marked.parse( dangerouslySetInnerHTML={{ __html : marked.parse(
message.text, message.text,
{ sanitize: true, renderer: linkRenderer } { sanitize: true, renderer: linkRenderer }
) }} ) }}
/> />
<span className='message-time'> <span className='message-time'>
{message.name} - {this.getTimeString(messageTime)} {message.name} - {this.getTimeString(messageTime)}
</span> </span>
</div>
</div>
</div> </div>
</div> );
</div> })
); }
}) </When>
:<div className='empty'> <Otherwise>
<p>No one has said anything yet...</p> <div className='empty'>
</div> <p>No one has said anything yet...</p>
} </div>
</Otherwise>
</Choose>
</div> </div>
); );
} }

View File

@ -110,29 +110,34 @@ class FileEntry extends Component
<img className='file-avatar' src={this.props.data.picture || DEFAULT_PICTURE} /> <img className='file-avatar' src={this.props.data.picture || DEFAULT_PICTURE} />
<div className='file-content'> <div className='file-content'>
{this.props.data.me ? ( <Choose>
<p>You shared a file.</p> <When condition={this.props.data.me}>
) : ( <p>You shared a file.</p>
<p>{this.props.data.name} shared a file.</p> </When>
)} <Otherwise>
<p>{this.props.data.name} shared a file.</p>
</Otherwise>
</Choose>
{!this.state.active && !this.state.files && ( <If condition={!this.state.active && !this.state.files}>
<div className='file-info'> <div className='file-info'>
{WebTorrent.WEBRTC_SUPPORT ? ( <Choose>
<span className='button' onClick={this.handleDownload}> <When condition={WebTorrent.WEBRTC_SUPPORT}>
<img src='resources/images/download-icon.svg' /> <span className='button' onClick={this.handleDownload}>
</span> <img src='resources/images/download-icon.svg' />
) : ( </span>
<p> </When>
Your browser does not support downloading files using WebTorrent. <Otherwise>
</p> <p>
)} Your browser does not support downloading files using WebTorrent.
</p>
</Otherwise>
</Choose>
<p>{magnet.decode(this.props.data.file.magnet).dn}</p> <p>{magnet.decode(this.props.data.file.magnet).dn}</p>
</div> </div>
)} </If>
{this.state.active && this.state.numPeers === 0 && ( <If condition={this.state.active && this.state.numPeers === 0}>
<Fragment> <Fragment>
<p> <p>
Locating peers Locating peers
@ -145,13 +150,13 @@ class FileEntry extends Component
</p> </p>
)} )}
</Fragment> </Fragment>
)} </If>
{this.state.active && this.state.numPeers > 0 && ( <If condition={this.state.active && this.state.numPeers > 0}>
<progress value={this.state.progress} /> <progress value={this.state.progress} />
)} </If>
{this.state.files && ( <If condition={this.state.files}>
<Fragment> <Fragment>
<p>Torrent finished downloading.</p> <p>Torrent finished downloading.</p>
@ -165,7 +170,7 @@ class FileEntry extends Component
</div> </div>
))} ))}
</Fragment> </Fragment>
)} </If>
</div> </div>
</div> </div>
); );

View File

@ -17,17 +17,23 @@ class SharedFilesList extends Component
return ( return (
<div className='shared-files'> <div className='shared-files'>
{ sharing.length > 0 ? <Choose>
sharing.map((entry, i) => ( <When condition={sharing.length > 0}>
<FileEntry {
data={entry} sharing.map((entry, i) => (
key={i} <FileEntry
/> data={entry}
)) key={i}
:<div className='empty'> />
<p>No one has shared files yet...</p> ))
</div> }
} </When>
<Otherwise>
<div className='empty'>
<p>No one has shared files yet...</p>
</div>
</Otherwise>
</Choose>
</div> </div>
); );
} }

View File

@ -4,7 +4,7 @@ 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 './Peer';
import HiddenPeers from './HiddenPeers'; import HiddenPeers from './HiddenPeers';
@ -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,7 +140,7 @@ class Filmstrip extends Component
name={activePeerName} name={activePeerName}
/> />
</div> </div>
)} </If>
</div> </div>
<div className='filmstrip'> <div className='filmstrip'>
@ -142,35 +148,37 @@ class Filmstrip extends Component
{ {
Object.keys(peers).map((peerName) => Object.keys(peers).map((peerName) =>
{ {
return ( <If
spotlights.find((spotlightsElement) => spotlightsElement === peerName)? condition={
<div spotlights.find((spotlightsElement) => spotlightsElement === peerName)
key={peerName} }
onClick={() => this.props.setSelectedPeer(peerName)} >
className={classnames('film', { <div
selected : this.props.selectedPeerName === peerName, key={peerName}
active : this.state.lastSpeaker === peerName onClick={() => roomClient.setSelectedPeer(peerName)}
})} className={classnames('film', {
> selected : this.props.selectedPeerName === peerName,
<div className='film-content'> active : this.state.lastSpeaker === peerName
<Peer })}
advancedMode={advancedMode} >
name={peerName} <div className='film-content'>
/> <Peer
</div> advancedMode={advancedMode}
name={peerName}
/>
</div> </div>
:null </div>
); </If>;
}) })
} }
</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 +187,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 +213,7 @@ const mapStateToProps = (state) =>
}; };
}; };
const mapDispatchToProps = { export default withRoomContext(connect(
setSelectedPeer : requestActions.setSelectedPeer
};
export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps undefined
)(Filmstrip); )(Filmstrip));

View File

@ -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

View File

@ -35,12 +35,11 @@ export default class FullView extends React.Component
muted={Boolean(true)} muted={Boolean(true)}
/> />
{videoProfile === 'none' ? <If condition={videoProfile === 'none'}>
<div className='spinner-container'> <div className='spinner-container'>
<Spinner /> <Spinner />
</div> </div>
:null </If>
}
</div> </div>
); );
} }

View File

@ -5,7 +5,7 @@ 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 './PeerView';
import ScreenView from './ScreenView'; import ScreenView from './ScreenView';
@ -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: &lsquo;m&lsquo;' data-tip='keyboard shortcut: &lsquo;m&lsquo;'
@ -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 =
{ {
connected : PropTypes.bool.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, connected : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired, advancedMode : PropTypes.bool,
micProducer : appPropTypes.Producer, me : appPropTypes.Me.isRequired,
webcamProducer : appPropTypes.Producer, micProducer : appPropTypes.Producer,
screenProducer : appPropTypes.Producer, webcamProducer : appPropTypes.Producer,
onChangeDisplayName : PropTypes.func.isRequired, screenProducer : appPropTypes.Producer
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;

View File

@ -3,18 +3,15 @@ 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.pausePeerScreen(peer.name) :
roomClient.resumePeerScreen(peer.name);
}} }}
/> />
: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.mutePeerAudio(peer.name) :
roomClient.unmutePeerAudio(peer.name);
}} }}
/> />
</div> </div>
@ -91,17 +89,12 @@ const ListPeer = (props) =>
ListPeer.propTypes = ListPeer.propTypes =
{ {
advancedMode : PropTypes.bool, roomClient : PropTypes.any.isRequired,
peer : appPropTypes.Peer.isRequired, advancedMode : PropTypes.bool,
micConsumer : appPropTypes.Consumer, peer : appPropTypes.Peer.isRequired,
webcamConsumer : appPropTypes.Consumer, micConsumer : appPropTypes.Consumer,
screenConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer,
onMuteMic : PropTypes.func.isRequired, screenConsumer : appPropTypes.Consumer
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;

View File

@ -2,16 +2,16 @@ 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;

View File

@ -3,7 +3,7 @@ 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 './PeerView';
import ScreenView from './ScreenView'; import ScreenView from './ScreenView';
@ -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.mutePeerAudio(peer.name) :
roomClient.unmutePeerAudio(peer.name);
}} }}
/> />
@ -162,7 +160,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', {
@ -195,8 +193,7 @@ class Peer extends Component
screenCodec={screenConsumer ? screenConsumer.codec : null} screenCodec={screenConsumer ? screenConsumer.codec : null}
/> />
</div> </div>
:null </If>
}
</div> </div>
); );
} }
@ -204,14 +201,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 +238,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 +251,9 @@ const mapDispatchToProps = (dispatch) =>
}; };
}; };
const PeerContainer = connect( const PeerContainer = withRoomContext(connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(Peer); )(Peer));
export default PeerContainer; export default PeerContainer;

View File

@ -52,51 +52,50 @@ 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>
<EditableInput <When condition={isMe}>
value={peer.displayName} <EditableInput
propName='displayName' value={peer.displayName}
className='display-name editable' propName='displayName'
classLoading='loading' className='display-name editable'
classInvalid='invalid' classLoading='loading'
shouldBlockWhileLoading classInvalid='invalid'
editProps={{ shouldBlockWhileLoading
maxLength : 20, editProps={{
autoCorrect : false, maxLength : 20,
spellCheck : false autoCorrect : false,
}} spellCheck : false
onChange={({ displayName }) => onChangeDisplayName(displayName)} }}
/> onChange={({ displayName }) => onChangeDisplayName(displayName)}
: />
<span className='display-name'> </When>
{peer.displayName} <Otherwise>
</span> <span className='display-name'>
} {peer.displayName}
</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 +104,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>
@ -125,12 +123,11 @@ export default class PeerView extends React.Component
<div className={classnames('bar', `level${volume}`)} /> <div className={classnames('bar', `level${volume}`)} />
</div> </div>
{videoProfile === 'none' ? <If condition={videoProfile === 'none'}>
<div className='spinner-container'> <div className='spinner-container'>
<Spinner /> <Spinner />
</div> </div>
:null </If>
}
</div> </div>
); );
} }

View File

@ -108,35 +108,36 @@ class Peers extends React.Component
{ {
peers.map((peer) => peers.map((peer) =>
{ {
return ( <If
(spotlights.find(function(spotlightsElement) condition={
{ return spotlightsElement == peer.name; }))? spotlights.find((spotlightsElement) => spotlightsElement === peer.name)
<Appear key={peer.name} duration={1000}> }
<div >
className={classnames('peer-container', { <Appear key={peer.name} duration={1000}>
'selected' : this.props.selectedPeerName === peer.name, <div
'active-speaker' : peer.name === activeSpeakerName className={classnames('peer-container', {
})} 'selected' : this.props.selectedPeerName === peer.name,
> 'active-speaker' : peer.name === activeSpeakerName
<div className='peer-content'> })}
<Peer >
advancedMode={advancedMode} <div className='peer-content'>
name={peer.name} <Peer
style={style} advancedMode={advancedMode}
/> name={peer.name}
</div> style={style}
/>
</div> </div>
</Appear> </div>
:null </Appear>
); </If>;
}) })
} }
<div className='hidden-peer-container'> <div className='hidden-peer-container'>
{ (spotlightsLength<peers.length)? <If condition={spotlightsLength < peers.length}>
<HiddenPeers <HiddenPeers
hiddenPeersCount={peers.length-spotlightsLength} hiddenPeersCount={peers.length-spotlightsLength}
/>:null />
} </If>
</div> </div>
</div> </div>
); );

View File

@ -13,7 +13,6 @@ import Me from './Me';
import Peers from './Peers'; import Peers from './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 './FullScreenView';
import VideoWindow from './VideoWindow/VideoWindow'; import VideoWindow from './VideoWindow/VideoWindow';
@ -97,13 +96,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', {

View File

@ -41,25 +41,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
@ -73,12 +69,11 @@ export default class ScreenView extends React.Component
muted={Boolean(true)} muted={Boolean(true)}
/> />
{screenProfile === 'none' ? <If condition={screenProfile === 'none'}>
<div className='spinner-container'> <div className='spinner-container'>
<Spinner /> <Spinner />
</div> </div>
:null </If>
}
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
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 PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Dropdown from 'react-dropdown'; import Dropdown from 'react-dropdown';
@ -18,8 +18,11 @@ const modes = [ {
const findOption = (options, value) => options.find((option) => option.value === value); const findOption = (options, value) => options.find((option) => option.value === value);
const Settings = ({ const Settings = ({
room, me, onToggleAdvancedMode, handleChangeWebcam, roomClient,
handleChangeAudioDevice, handleChangeMode room,
me,
onToggleAdvancedMode,
handleChangeMode
}) => }) =>
{ {
let webcams; let webcams;
@ -48,7 +51,7 @@ const Settings = ({
<Dropdown <Dropdown
options={webcams} options={webcams}
value={findOption(webcams, me.selectedWebcam)} value={findOption(webcams, me.selectedWebcam)}
onChange={(webcam) => handleChangeWebcam(webcam.value)} onChange={(webcam) => roomClient.changeWebcam(webcam.value)}
placeholder={'Select camera'} placeholder={'Select camera'}
/> />
@ -56,7 +59,7 @@ const Settings = ({
disabled={!me.canChangeAudioDevice} disabled={!me.canChangeAudioDevice}
options={audioDevices} options={audioDevices}
value={findOption(audioDevices, me.selectedAudioDevice)} value={findOption(audioDevices, me.selectedAudioDevice)}
onChange={(device) => handleChangeAudioDevice(device.value)} onChange={(device) => roomClient.changeAudioDevice(device.value)}
placeholder={audioDevicesText} placeholder={audioDevicesText}
/> />
<ReactTooltip <ReactTooltip
@ -94,12 +97,11 @@ const Settings = ({
Settings.propTypes = Settings.propTypes =
{ {
me : appPropTypes.Me.isRequired, roomClient : PropTypes.any.isRequired,
room : appPropTypes.Room.isRequired, me : appPropTypes.Me.isRequired,
handleChangeWebcam : PropTypes.func.isRequired, room : appPropTypes.Room.isRequired,
handleChangeAudioDevice : PropTypes.func.isRequired, onToggleAdvancedMode : PropTypes.func.isRequired,
onToggleAdvancedMode : PropTypes.func.isRequired, handleChangeMode : PropTypes.func.isRequired
handleChangeMode : PropTypes.func.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
@ -111,15 +113,13 @@ const mapStateToProps = (state) =>
}; };
const mapDispatchToProps = { const mapDispatchToProps = {
handleChangeWebcam : requestActions.changeWebcam, onToggleAdvancedMode : stateActions.toggleAdvancedMode,
handleChangeAudioDevice : requestActions.changeAudioDevice, handleChangeMode : stateActions.setDisplayMode
onToggleAdvancedMode : stateActions.toggleAdvancedMode,
handleChangeMode : stateActions.setDisplayMode
}; };
const SettingsContainer = connect( const SettingsContainer = withRoomContext(connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(Settings); )(Settings));
export default SettingsContainer; export default SettingsContainer;

View File

@ -3,7 +3,7 @@ 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}>
<div <Choose>
className='button logout' <When condition={me.loggedIn}>
data-tip='Logout' <div
data-place='right' className='button logout'
data-type='dark' data-tip='Logout'
onClick={onLogout} data-place='right'
> data-type='dark'
<img src={me.picture || 'resources/images/avatar-empty.jpeg'} /> onClick={() => roomClient.logout()}
</div> >
) : ( <img src={me.picture || 'resources/images/avatar-empty.jpeg'} />
<div </div>
className='button login off' </When>
data-tip='Login' <Otherwise>
data-place='right' <div
data-type='dark' className='button login off'
onClick={onLogin} data-tip='Login'
/> data-place='right'
))} data-type='dark'
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);

View File

@ -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}>
<Room /> <RoomContext.Provider value={roomClient}>
<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);
}; };

View File

@ -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 }) =>

View File

@ -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);
};
};

View File

@ -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')

View File

@ -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",

View File

@ -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
}
}
};

View File

@ -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'));

View File

@ -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>

View File

@ -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);
};

View File

@ -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