From 06b480f4f43a0535603784c2329ebe6d09a080e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 12:47:16 +0100 Subject: [PATCH 01/15] Added actions and updated middleware and reducer. --- app/lib/redux/reducers/me.js | 35 +++++++++++++++++---------- app/lib/redux/requestActions.js | 14 +++++++++++ app/lib/redux/roomClientMiddleware.js | 14 +++++++++++ app/lib/redux/stateActions.js | 12 +++++++-- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/app/lib/redux/reducers/me.js b/app/lib/redux/reducers/me.js index b692b1d..be24a81 100644 --- a/app/lib/redux/reducers/me.js +++ b/app/lib/redux/reducers/me.js @@ -1,16 +1,18 @@ const initialState = { - name : null, - displayName : null, - displayNameSet : false, - device : null, - canSendMic : false, - canSendWebcam : false, - canChangeWebcam : false, - webcamInProgress : false, - audioOnly : false, - audioOnlyInProgress : false, - restartIceInProgress : false + name : null, + displayName : null, + displayNameSet : false, + device : null, + canSendMic : false, + canSendWebcam : false, + canShareScreen : true, + canChangeWebcam : false, + webcamInProgress : false, + screenShareInProgress : false, + audioOnly : false, + audioOnlyInProgress : false, + restartIceInProgress : false }; const me = (state = initialState, action) => @@ -26,9 +28,9 @@ const me = (state = initialState, action) => case 'SET_MEDIA_CAPABILITIES': { - const { canSendMic, canSendWebcam } = action.payload; + const { canSendMic, canSendWebcam, canShareScreen } = action.payload; - return { ...state, canSendMic, canSendWebcam }; + return { ...state, canSendMic, canSendWebcam, canShareScreen }; } case 'SET_CAN_CHANGE_WEBCAM': @@ -45,6 +47,13 @@ const me = (state = initialState, action) => return { ...state, webcamInProgress: flag }; } + case 'SET_SCREEN_SHARE_IN_PROGRESS': + { + const { flag } = action.payload; + + return { ...state, screenShareInProgress: flag }; + } + case 'SET_DISPLAY_NAME': { let { displayName } = action.payload; diff --git a/app/lib/redux/requestActions.js b/app/lib/redux/requestActions.js index 14210ef..5a53fb5 100644 --- a/app/lib/redux/requestActions.js +++ b/app/lib/redux/requestActions.js @@ -85,6 +85,20 @@ export const restartIce = () => }; }; +export const enableScreenSharing = () => +{ + return { + type : 'ENABLE_SCREEN_SHARING' + }; +}; + +export const disableScreenSharing = () => +{ + return { + type : 'DISABLE_SCREEN_SHARING' + }; +}; + export const sendChatMessage = (text, name) => { const message = createNewMessage(text, 'response', name); diff --git a/app/lib/redux/roomClientMiddleware.js b/app/lib/redux/roomClientMiddleware.js index 271d700..9ba36a8 100644 --- a/app/lib/redux/roomClientMiddleware.js +++ b/app/lib/redux/roomClientMiddleware.js @@ -109,6 +109,20 @@ export default ({ dispatch, getState }) => (next) => break; } + case 'ENABLE_SCREEN_SHARING': + { + client.enableScreenSharing(); + + break; + } + + case 'DISABLE_SCREEN_SHARING': + { + client.disableScreenSharing(); + + break; + } + case 'SEND_CHAT_MESSAGE': { const { message } = action.payload; diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index bd19225..c788b25 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -30,11 +30,11 @@ export const setMe = ({ peerName, displayName, displayNameSet, device }) => }; }; -export const setMediaCapabilities = ({ canSendMic, canSendWebcam }) => +export const setMediaCapabilities = ({ canSendMic, canSendWebcam, canShareScreen }) => { return { type : 'SET_MEDIA_CAPABILITIES', - payload : { canSendMic, canSendWebcam } + payload : { canSendMic, canSendWebcam, canShareScreen } }; }; @@ -126,6 +126,14 @@ export const setWebcamInProgress = (flag) => }; }; +export const setScreenShareInProgress = (flag) => +{ + return { + type : 'SET_SCREEN_SHARE_IN_PROGRESS', + payload : { flag } + }; +}; + export const addPeer = (peer) => { return { From e99cbf0ec9c15c740df823b28d24b04ce38d3823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 12:49:08 +0100 Subject: [PATCH 02/15] Preparation for Chrome screen sharing plugin. --- .gitignore | 2 ++ app/config.example.js | 4 +++ app/gulpfile.js | 8 +++++ app/index.html | 1 + app/package-lock.json | 77 ++++++++++++++++++++++++++++++++++++++++--- app/package.json | 1 + 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 app/config.example.js diff --git a/.gitignore b/.gitignore index 30d8e6c..01f5a19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ node_modules/ +/app/config.* +!/app/config.example.js /server/config.* !/server/config.example.js /server/public/ diff --git a/app/config.example.js b/app/config.example.js new file mode 100644 index 0000000..7734362 --- /dev/null +++ b/app/config.example.js @@ -0,0 +1,4 @@ +module.exports = +{ + chromeExtension : 'https://chrome.google.com/webstore/detail/fckajcjdaabdgnbdcmhhebdglogjfodi' +}; diff --git a/app/gulpfile.js b/app/gulpfile.js index 6f06c06..3eaa076 100644 --- a/app/gulpfile.js +++ b/app/gulpfile.js @@ -20,6 +20,7 @@ const gulpif = require('gulp-if'); const gutil = require('gulp-util'); const plumber = require('gulp-plumber'); const rename = require('gulp-rename'); +const change = require('gulp-change'); const header = require('gulp-header'); const touch = require('gulp-touch-cmd'); const browserify = require('browserify'); @@ -45,6 +46,7 @@ const BANNER_OPTIONS = currentYear : (new Date()).getFullYear() }; const OUTPUT_DIR = '../server/public'; +const appOptions = require('./config'); // Set Node 'development' environment (unless externally set). process.env.NODE_ENV = process.env.NODE_ENV || 'development'; @@ -123,6 +125,11 @@ function bundle(options) return rebundle(); } +function changeHTML(content) +{ + return content.replace(/chromeExtension/g, appOptions.chromeExtension); +} + gulp.task('clean', () => del(OUTPUT_DIR, { force: true })); gulp.task('lint', () => @@ -163,6 +170,7 @@ gulp.task('css', () => gulp.task('html', () => { return gulp.src('index.html') + .pipe(change(changeHTML)) .pipe(gulp.dest(OUTPUT_DIR)); }); diff --git a/app/index.html b/app/index.html index 6f4ba8a..ea79bc7 100644 --- a/app/index.html +++ b/app/index.html @@ -8,6 +8,7 @@ + diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index ab5b7a4..5109311 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -6,7 +6,6 @@ import { getProtooUrl } from './urlFactory'; import * as cookiesManager from './cookiesManager'; import * as requestActions from './redux/requestActions'; import * as stateActions from './redux/stateActions'; -import { getBrowserType } from './utils'; const logger = new Logger('RoomClient'); @@ -81,8 +80,7 @@ export default class RoomClient // Local Webcam. Object with: // - {MediaDeviceInfo} [device] // - {String} [resolution] - 'qvga' / 'vga' / 'hd'. - this._webcam = - { + this._webcam = { device : null, resolution : 'hd' }; @@ -195,6 +193,57 @@ export default class RoomClient this._micProducer.resume(); } + installExtension() + { + logger.debug('installExtension()'); + + return Promise.resolve() + .then(() => + { + window.addEventListener('message', _onExtensionMessage, false); + function _onExtensionMessage({ data }) + { + if (data.type === 'ScreenShareInjected') + { + logger.debug('installExtension() | installation succeeded'); + + return; + } + } + + function _failedInstall(reason) + { + window.removeEventListener('message', _onExtensionMessage); + + return Promise.reject( + new Error('Failed to install extension: %s', reason)); + } + + function _successfulInstall() + { + logger.debug('installExtension() | installation accepted'); + } + + // eslint-disable-next-line no-undef + chrome.webstore.install(null, _successfulInstall, _failedInstall); + }) + .then(() => + { + this._dispatch(stateActions.setScreenCapabilities( + { + canShareScreen : true, + needExtension : false + })); + }) + .catch((error) => + { + logger.error('enableScreenSharing() | failed: %o', error); + + this._dispatch( + stateActions.setScreenShareInProgress(false)); + }); + } + enableScreenSharing() { logger.debug('enableScreenSharing()'); @@ -203,37 +252,6 @@ export default class RoomClient stateActions.setScreenShareInProgress(true)); return Promise.resolve() - .then(() => - { - const browser = getBrowserType(); - - switch (browser) - { - case 'chrome': - { - // Check if we have extension, if not, try to install - // if (!('__multipartyMeetingScreenShareExtensionAvailable__' in window)) - // { - // window.addEventListener('message', function(ev) - // { - // if (ev.data.type === 'ScreenShareInjected') - // { - // } - // }, false); - // } - break; - } - case 'firefox': - { - break; - } - default: - { - return Promise.reject( - new Error('Unsupported browser for screen sharing')); - } - } - }) .then(() => { return this._setScreenShareProducer(); @@ -820,9 +838,14 @@ export default class RoomClient // Set our media capabilities. this._dispatch(stateActions.setMediaCapabilities( { - canSendMic : this._room.canSend('audio'), - canSendWebcam : this._room.canSend('video'), - canShareScreen : this._room.canSend('video') + canSendMic : this._room.canSend('audio'), + canSendWebcam : this._room.canSend('video') + })); + this._dispatch(stateActions.setScreenCapabilities( + { + canShareScreen : this._room.canSend('video') && + this._screenSharing.isScreenShareAvailable(), + needExtension : this._screenSharing.needExtension() })); }) .then(() => @@ -1007,7 +1030,8 @@ export default class RoomClient return Promise.resolve() .then(() => { - const available = this._screenSharing.isScreenShareAvailable(); + const available = this._screenSharing.isScreenShareAvailable() && + !this._screenSharing.needExtension(); if (!available) throw new Error('screen sharing not available'); diff --git a/app/lib/ScreenShare.js b/app/lib/ScreenShare.js index b598bc6..36d2760 100644 --- a/app/lib/ScreenShare.js +++ b/app/lib/ScreenShare.js @@ -63,6 +63,16 @@ class ChromeScreenShare return false; } + + needExtension() + { + if ('__multipartyMeetingScreenShareExtensionAvailable__' in window) + { + return false; + } + + return true; + } _toConstraints(options, streamId) { @@ -135,6 +145,11 @@ class FirefoxScreenShare return true; } + needExtension() + { + return false; + } + _toConstraints(options) { const constraints = { diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx index 74284bc..2f16175 100644 --- a/app/lib/components/Room.jsx +++ b/app/lib/components/Room.jsx @@ -20,12 +20,27 @@ class Room extends React.Component room, me, amActiveSpeaker, + screenProducer, onRoomLinkCopy, onSetAudioMode, onRestartIce, - onLeaveMeeting + onLeaveMeeting, + onShareScreen, + onUnShareScreen, + onNeedExtension } = this.props; + let screenState; + + if (me.needExtension) + screenState = 'need-extension'; + else if (!me.canShareScreen) + screenState = 'unsupported'; + else if (screenProducer) + screenState = 'on'; + else + screenState = 'off'; + return (
@@ -79,6 +94,37 @@ class Room extends React.Component
+
+ { + switch (screenState) + { + case 'on': + { + onUnShareScreen(); + break; + } + case 'off': + { + onShareScreen(); + break; + } + case 'need-extension': + { + onNeedExtension(); + break; + } + default: + { + break; + } + } + }} + /> +
{ + const producersArray = Object.values(state.producers); + const screenProducer = + producersArray.find((producer) => producer.source === 'screen'); + return { room : state.room, me : state.me, - amActiveSpeaker : state.me.name === state.room.activeSpeakerName + amActiveSpeaker : state.me.name === state.room.activeSpeakerName, + screenProducer : screenProducer }; }; @@ -161,6 +216,18 @@ const mapDispatchToProps = (dispatch) => onLeaveMeeting : () => { dispatch(requestActions.leaveRoom()); + }, + onShareScreen : () => + { + dispatch(requestActions.enableScreenSharing()); + }, + onUnShareScreen : () => + { + dispatch(requestActions.disableScreenSharing()); + }, + onNeedExtension : () => + { + dispatch(requestActions.installExtension()); } }; }; diff --git a/app/lib/redux/reducers/me.js b/app/lib/redux/reducers/me.js index be24a81..dd6b41c 100644 --- a/app/lib/redux/reducers/me.js +++ b/app/lib/redux/reducers/me.js @@ -6,7 +6,8 @@ const initialState = device : null, canSendMic : false, canSendWebcam : false, - canShareScreen : true, + canShareScreen : false, + needExtension : false, canChangeWebcam : false, webcamInProgress : false, screenShareInProgress : false, @@ -28,9 +29,16 @@ const me = (state = initialState, action) => case 'SET_MEDIA_CAPABILITIES': { - const { canSendMic, canSendWebcam, canShareScreen } = action.payload; + const { canSendMic, canSendWebcam } = action.payload; - return { ...state, canSendMic, canSendWebcam, canShareScreen }; + return { ...state, canSendMic, canSendWebcam }; + } + + case 'SET_SCREEN_CAPABILITIES': + { + const { canShareScreen, needExtension } = action.payload; + + return { ...state, canShareScreen, needExtension }; } case 'SET_CAN_CHANGE_WEBCAM': diff --git a/app/lib/redux/requestActions.js b/app/lib/redux/requestActions.js index 5a53fb5..1c3b304 100644 --- a/app/lib/redux/requestActions.js +++ b/app/lib/redux/requestActions.js @@ -99,6 +99,13 @@ export const disableScreenSharing = () => }; }; +export const installExtension = () => +{ + return { + type : 'INSTALL_EXTENSION' + }; +}; + export const sendChatMessage = (text, name) => { const message = createNewMessage(text, 'response', name); diff --git a/app/lib/redux/roomClientMiddleware.js b/app/lib/redux/roomClientMiddleware.js index 9ba36a8..fed0951 100644 --- a/app/lib/redux/roomClientMiddleware.js +++ b/app/lib/redux/roomClientMiddleware.js @@ -123,6 +123,13 @@ export default ({ dispatch, getState }) => (next) => break; } + case 'INSTALL_EXTENSION': + { + client.installExtension(); + + break; + } + case 'SEND_CHAT_MESSAGE': { const { message } = action.payload; diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index c788b25..c55bfcd 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -30,11 +30,19 @@ export const setMe = ({ peerName, displayName, displayNameSet, device }) => }; }; -export const setMediaCapabilities = ({ canSendMic, canSendWebcam, canShareScreen }) => +export const setMediaCapabilities = ({ canSendMic, canSendWebcam }) => { return { type : 'SET_MEDIA_CAPABILITIES', - payload : { canSendMic, canSendWebcam, canShareScreen } + payload : { canSendMic, canSendWebcam } + }; +}; + +export const setScreenCapabilities = ({ canShareScreen, needExtension }) => +{ + return { + type : 'SET_SCREEN_CAPABILITIES', + payload : { canShareScreen, needExtension } }; }; diff --git a/app/resources/images/share-screen-extension.svg b/app/resources/images/share-screen-extension.svg new file mode 100644 index 0000000..8d30bb3 --- /dev/null +++ b/app/resources/images/share-screen-extension.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/stylus/components/Room.styl b/app/stylus/components/Room.styl index d305da7..8f8ec1e 100644 --- a/app/stylus/components/Room.styl +++ b/app/stylus/components/Room.styl @@ -234,20 +234,20 @@ &.screen { &.on { - background-image: url('/resources/images/unshare-screen.svg'); + background-image: url('/resources/images/no-share-screen-black.svg'); } &.off { - background-image: url('/resources/images/share-screen.svg'); + background-image: url('/resources/images/share-screen-black.svg'); } &.unsupported { - background-image: url('/resources/images/unshare-screen.svg'); + background-image: url('/resources/images/no-share-screen-white.svg'); background-color: rgba(#d42241, 0.7); } &.need-extension { - background-image: url('/resources/images/share-screen.svg'); + background-image: url('/resources/images/share-screen-extension.svg'); } } From a0047f54bb31024c02d39c1d63c7593afe6ace84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 22:24:42 +0100 Subject: [PATCH 10/15] Updated screen sharing styling and icon --- app/lib/components/Room.jsx | 15 ++++++++++++++- app/resources/images/share-screen-white.svg | 11 +++++++++++ app/stylus/components/Room.styl | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 app/resources/images/share-screen-white.svg diff --git a/app/lib/components/Room.jsx b/app/lib/components/Room.jsx index 2f16175..eb19f99 100644 --- a/app/lib/components/Room.jsx +++ b/app/lib/components/Room.jsx @@ -31,15 +31,28 @@ class Room extends React.Component } = this.props; let screenState; + let screenTip; if (me.needExtension) + { screenState = 'need-extension'; + screenTip = 'Install screen sharing extension'; + } else if (!me.canShareScreen) + { screenState = 'unsupported'; + screenTip = 'Screen sharing not supported'; + } else if (screenProducer) + { screenState = 'on'; + screenTip = 'Stop screen sharing'; + } else + { screenState = 'off'; + screenTip = 'Start screen sharing'; + } return ( @@ -96,7 +109,7 @@ class Room extends React.Component
{ diff --git a/app/resources/images/share-screen-white.svg b/app/resources/images/share-screen-white.svg new file mode 100644 index 0000000..ebf3f40 --- /dev/null +++ b/app/resources/images/share-screen-white.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/app/stylus/components/Room.styl b/app/stylus/components/Room.styl index 8f8ec1e..fcc1471 100644 --- a/app/stylus/components/Room.styl +++ b/app/stylus/components/Room.styl @@ -238,7 +238,7 @@ } &.off { - background-image: url('/resources/images/share-screen-black.svg'); + background-image: url('/resources/images/share-screen-white.svg'); } &.unsupported { From 2a08722cf39efb26e5f06c0138d8c9eb0cb9c21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 22:28:29 +0100 Subject: [PATCH 11/15] More icon and styling --- app/resources/images/chat-icon.svg | 2 +- app/stylus/components/Chat.styl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/resources/images/chat-icon.svg b/app/resources/images/chat-icon.svg index 5273589..172defc 100644 --- a/app/resources/images/chat-icon.svg +++ b/app/resources/images/chat-icon.svg @@ -2,7 +2,7 @@ - + diff --git a/app/stylus/components/Chat.styl b/app/stylus/components/Chat.styl index 2836c4b..45f5b63 100644 --- a/app/stylus/components/Chat.styl +++ b/app/stylus/components/Chat.styl @@ -15,7 +15,7 @@ background-position: center; background-size: 70%; background-repeat: no-repeat; - background-color: rgba(#000, 0.5); + background-color: rgba(#fff, 0.3); background-image: url('/resources/images/chat-icon.svg'); cursor: pointer; transition-property: opacity, background-color; From a1caff2af3fc7ed6a50b0312c6169f57fdebbd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Sun, 4 Mar 2018 23:28:24 +0100 Subject: [PATCH 12/15] Fixed bug in extension install code. --- app/lib/RoomClient.js | 66 ++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/app/lib/RoomClient.js b/app/lib/RoomClient.js index 5109311..06654b0 100644 --- a/app/lib/RoomClient.js +++ b/app/lib/RoomClient.js @@ -197,50 +197,46 @@ export default class RoomClient { logger.debug('installExtension()'); - return Promise.resolve() - .then(() => - { - window.addEventListener('message', _onExtensionMessage, false); - function _onExtensionMessage({ data }) - { - if (data.type === 'ScreenShareInjected') - { - logger.debug('installExtension() | installation succeeded'); - - return; - } - } - - function _failedInstall(reason) - { - window.removeEventListener('message', _onExtensionMessage); - - return Promise.reject( - new Error('Failed to install extension: %s', reason)); - } - - function _successfulInstall() - { - logger.debug('installExtension() | installation accepted'); - } - - // eslint-disable-next-line no-undef - chrome.webstore.install(null, _successfulInstall, _failedInstall); - }) + return new Promise((resolve, reject) => + { + window.addEventListener('message', _onExtensionMessage, false); + // eslint-disable-next-line no-undef + chrome.webstore.install(null, _successfulInstall, _failedInstall); + function _onExtensionMessage({ data }) + { + if (data.type === 'ScreenShareInjected') + { + logger.debug('installExtension() | installation succeeded'); + + return resolve(); + } + } + + function _failedInstall(reason) + { + window.removeEventListener('message', _onExtensionMessage); + + return reject( + new Error('Failed to install extension: %s', reason)); + } + + function _successfulInstall() + { + logger.debug('installExtension() | installation accepted'); + } + }) .then(() => { + // This should be handled better this._dispatch(stateActions.setScreenCapabilities( { - canShareScreen : true, + canShareScreen : this._room.canSend('video'), needExtension : false })); }) .catch((error) => { - logger.error('enableScreenSharing() | failed: %o', error); - - this._dispatch( - stateActions.setScreenShareInProgress(false)); + logger.error('installExtension() | failed: %o', error); }); } From 341dee4f9d298975fa536f2c9141369c8c460504 Mon Sep 17 00:00:00 2001 From: Stefan Otto Date: Thu, 22 Mar 2018 13:30:19 +0100 Subject: [PATCH 13/15] first working layout for democratic view --- app/lib/components/Peers.jsx | 139 +++++++++++++++++++++++++------ app/lib/redux/STATE.md | 2 + app/lib/redux/reducers/room.js | 11 ++- app/lib/redux/stateActions.js | 8 ++ app/stylus/components/Peers.styl | 5 +- app/stylus/index.styl | 2 +- 6 files changed, 138 insertions(+), 29 deletions(-) diff --git a/app/lib/components/Peers.jsx b/app/lib/components/Peers.jsx index 789454e..fdd4a92 100644 --- a/app/lib/components/Peers.jsx +++ b/app/lib/components/Peers.jsx @@ -3,37 +3,123 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import * as appPropTypes from './appPropTypes'; +import * as stateActions from '../redux/stateActions'; import { Appear } from './transitions'; import Peer from './Peer'; -const Peers = ({ peers, activeSpeakerName }) => +class Peers extends React.Component { - return ( -
+ constructor() + { + super(); + this.state = { + ratio : 4 / 3 + }; + + } + updateDimensions() + { + const n = this.props.peers.length; + + if (n == 0) + { + return; + } + + const width = this.refs.peers.clientWidth; + const height = this.refs.peers.clientHeight; + + let x, y, space; + + for (let rows = 1; rows < 100; rows = rows + 1) + { + x = width / Math.ceil(n / rows); + y = x / this.state.ratio; + if (height < (y * rows)) { - peers.map((peer) => - { - return ( - -
- -
-
- ); - }) + y = height / rows; + x = this.state.ratio * y; + break; } -
- ); -}; + space = height - (y * (rows)); + if (space < y) + { + break; + } + } + if (Math.ceil(this.props.peerWidth) !== Math.ceil(0.9 * x)) + { + this.props.onComponentResize(0.9 * x, 0.9 * y); + } + } + + componentDidMount() + { + window.addEventListener('resize', this.updateDimensions.bind(this)); + } + + componentWillUnmount() + { + window.removeEventListener('resize', this.updateDimensions.bind(this)); + } + + render() + { + const { + activeSpeakerName, + peers, + peerWidth, + peerHeight + } = this.props; + + const style = + { + 'width' : peerWidth, + 'height' : peerHeight + }; + + this.updateDimensions(); + + return ( +
+ { + peers.map((peer) => + { + return ( + +
+ +
+
+ ); + }) + } +
+ ); + } +} Peers.propTypes = { peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired, - activeSpeakerName : PropTypes.string + activeSpeakerName : PropTypes.string, + peerHeight : PropTypes.number, + peerWidth : PropTypes.number, + onComponentResize : PropTypes.func.isRequired +}; + +const mapDispatchToProps = (dispatch) => +{ + return { + onComponentResize : (peerWidth, peerHeight) => + { + dispatch(stateActions.onComponentResize(peerWidth, peerHeight)); + } + }; }; const mapStateToProps = (state) => @@ -41,13 +127,18 @@ const mapStateToProps = (state) => // TODO: This is not OK since it's creating a new array every time, so triggering a // component rendering. const peersArray = Object.values(state.peers); - + return { peers : peersArray, - activeSpeakerName : state.room.activeSpeakerName + activeSpeakerName : state.room.activeSpeakerName, + peerHeight : state.room.peerHeight, + peerWidth : state.room.peerWidth }; }; -const PeersContainer = connect(mapStateToProps)(Peers); +const PeersContainer = connect( + mapStateToProps, + mapDispatchToProps +)(Peers); export default PeersContainer; diff --git a/app/lib/redux/STATE.md b/app/lib/redux/STATE.md index 3171af9..c05ffc2 100644 --- a/app/lib/redux/STATE.md +++ b/app/lib/redux/STATE.md @@ -2,6 +2,8 @@ ```js { + peerWidth : 200, + peerHeight : 150, room : { url : 'https://example.io/?&roomId=d0el8y34', diff --git a/app/lib/redux/reducers/room.js b/app/lib/redux/reducers/room.js index 24d8a04..622ba71 100644 --- a/app/lib/redux/reducers/room.js +++ b/app/lib/redux/reducers/room.js @@ -2,7 +2,9 @@ const initialState = { url : null, state : 'new', // new/connecting/connected/disconnected/closed, - activeSpeakerName : null + activeSpeakerName : null, + peerHeight : 300, + peerWidth : 400 }; const room = (state = initialState, action) => @@ -33,6 +35,13 @@ const room = (state = initialState, action) => return { ...state, activeSpeakerName: peerName }; } + case 'SET_COMPONENT_SIZE': + { + const { peerWidth, peerHeight } = action.payload; + + return { ...state, peerWidth: peerWidth, peerHeight: peerHeight }; + } + default: return state; } diff --git a/app/lib/redux/stateActions.js b/app/lib/redux/stateActions.js index bd19225..c5e72b0 100644 --- a/app/lib/redux/stateActions.js +++ b/app/lib/redux/stateActions.js @@ -22,6 +22,14 @@ export const setRoomActiveSpeaker = (peerName) => }; }; +export const onComponentResize = (peerWidth, peerHeight) => +{ + return { + type : 'SET_COMPONENT_SIZE', + payload : { peerWidth, peerHeight } + }; +}; + export const setMe = ({ peerName, displayName, displayNameSet, device }) => { return { diff --git a/app/stylus/components/Peers.styl b/app/stylus/components/Peers.styl index 7f72b52..0cd52e5 100644 --- a/app/stylus/components/Peers.styl +++ b/app/stylus/components/Peers.styl @@ -1,10 +1,11 @@ [data-component='Peers'] { min-height: 100%; width: 100%; + height: 100%; +desktop() { width: 100%; - padding: 40px 0 140px 0; + padding: 0px 0 0px 0; display: flex; flex-direction: row; flex-wrap: wrap; @@ -29,8 +30,6 @@ +desktop() { flex: 0 0 auto; - height: 382px; - width: 450px; margin: 6px; border: 1px solid rgba(#fff, 0.15); box-shadow: 0px 5px 12px 2px rgba(#111, 0.5); diff --git a/app/stylus/index.styl b/app/stylus/index.styl index e96aa6d..f73898b 100644 --- a/app/stylus/index.styl +++ b/app/stylus/index.styl @@ -48,7 +48,7 @@ body { #multiparty-meeting-media-query-detector { position: relative; z-index: -1000; - bottom: 0; + bottom: 1px; left: 0; height: 1px; width: 1px; From acb035c9528e20233cf8464977de68ed1c6523aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Thu, 12 Apr 2018 11:42:10 +0200 Subject: [PATCH 14/15] Added basic support for screen layout --- app/lib/components/Peer.jsx | 95 +++++++++++++++++++++++++------------ app/stylus/index.styl | 1 + 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/app/lib/components/Peer.jsx b/app/lib/components/Peer.jsx index 61ed633..66536eb 100644 --- a/app/lib/components/Peer.jsx +++ b/app/lib/components/Peer.jsx @@ -2,13 +2,15 @@ import React from 'react'; import { connect } from 'react-redux'; import * as appPropTypes from './appPropTypes'; import PeerView from './PeerView'; +import PeerScreenView from './PeerScreenView'; const Peer = (props) => { const { peer, micConsumer, - webcamConsumer + webcamConsumer, + screenConsumer } = props; const micEnabled = ( @@ -23,49 +25,77 @@ const Peer = (props) => !webcamConsumer.remotelyPaused ); + const screenVisible = ( + Boolean(screenConsumer) && + !screenConsumer.locallyPaused && + !screenConsumer.remotelyPaused + ); + let videoProfile; if (webcamConsumer) videoProfile = webcamConsumer.profile; - return ( -
-
- {!micEnabled ? -
- :null - } - {!videoVisible ? -
- :null - } + let screenProfile; + + if (screenConsumer) + screenProfile = screenConsumer.profile; + + if (screenVisible && screenConsumer.supported) + { + return ( +
+
- - {videoVisible && !webcamConsumer.supported ? -
-

incompatible video

+ ); + } + else + { + return ( +
+
+ {!micEnabled ? +
+ :null + } + {!videoVisible ? +
+ :null + }
- :null - } - -
- ); + {videoVisible && !webcamConsumer.supported ? +
+

incompatible video

+
+ :null + } + + +
+ ); + } }; Peer.propTypes = { peer : appPropTypes.Peer.isRequired, micConsumer : appPropTypes.Consumer, - webcamConsumer : appPropTypes.Consumer + webcamConsumer : appPropTypes.Consumer, + screenConsumer : appPropTypes.Consumer }; const mapStateToProps = (state, { name }) => @@ -77,11 +107,14 @@ const mapStateToProps = (state, { name }) => consumersArray.find((consumer) => consumer.source === 'mic'); const webcamConsumer = consumersArray.find((consumer) => consumer.source === 'webcam'); + const screenConsumer = + consumersArray.find((consumer) => consumer.source === 'screen'); return { peer, micConsumer, - webcamConsumer + webcamConsumer, + screenConsumer }; }; diff --git a/app/stylus/index.styl b/app/stylus/index.styl index f73898b..db3084e 100644 --- a/app/stylus/index.styl +++ b/app/stylus/index.styl @@ -40,6 +40,7 @@ body { @import './components/Peers'; @import './components/Peer'; @import './components/PeerView'; + @import './components/PeerScreenView'; @import './components/Notifications'; @import './components/Chat'; } From 1250396ba74358c377744cce725c2def930c2a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5var=20Aamb=C3=B8=20Fosstveit?= Date: Fri, 13 Apr 2018 09:54:59 +0200 Subject: [PATCH 15/15] Fixed bug in screen sharing, and added support for showing cam video at the same time as screen sharing --- app/lib/components/Peer.jsx | 77 +++++------ app/lib/components/PeerScreenView.jsx | 160 ---------------------- app/lib/components/PeerView.jsx | 124 +++++++++++++---- app/stylus/components/PeerScreenView.styl | 146 -------------------- app/stylus/components/PeerView.styl | 38 ++++- app/stylus/index.styl | 1 - 6 files changed, 159 insertions(+), 387 deletions(-) delete mode 100644 app/lib/components/PeerScreenView.jsx delete mode 100644 app/stylus/components/PeerScreenView.styl diff --git a/app/lib/components/Peer.jsx b/app/lib/components/Peer.jsx index 66536eb..09c4fa2 100644 --- a/app/lib/components/Peer.jsx +++ b/app/lib/components/Peer.jsx @@ -2,7 +2,6 @@ import React from 'react'; import { connect } from 'react-redux'; import * as appPropTypes from './appPropTypes'; import PeerView from './PeerView'; -import PeerScreenView from './PeerScreenView'; const Peer = (props) => { @@ -41,53 +40,41 @@ const Peer = (props) => if (screenConsumer) screenProfile = screenConsumer.profile; - if (screenVisible && screenConsumer.supported) - { - return ( -
- -
- ); - } - else - { - return ( -
-
- {!micEnabled ? -
- :null - } - {!videoVisible ? -
- :null - } -
- - {videoVisible && !webcamConsumer.supported ? -
-

incompatible video

-
+ return ( +
+
+ {!micEnabled ? +
+ :null + } + {!videoVisible ? +
:null } - -
- ); - } + + {videoVisible && !webcamConsumer.supported ? +
+

incompatible video

+
+ :null + } + + +
+ ); }; Peer.propTypes = diff --git a/app/lib/components/PeerScreenView.jsx b/app/lib/components/PeerScreenView.jsx deleted file mode 100644 index 62672aa..0000000 --- a/app/lib/components/PeerScreenView.jsx +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import Spinner from 'react-spinner'; - -export default class PeerScreenView extends React.Component -{ - constructor(props) - { - super(props); - - this.state = - { - videoWidth : null, - videoHeight : null - }; - - // Latest received video track. - // @type {MediaStreamTrack} - this._videoTrack = null; - - // Periodic timer for showing video resolution. - this._videoResolutionTimer = null; - } - - render() - { - const { - isMe, - videoVisible, - videoProfile, - videoCodec - } = this.props; - - const { - videoWidth, - videoHeight - } = this.state; - - return ( -
-
-
-
- {videoCodec ? -

{videoCodec} {videoProfile}

- :null - } - - {(videoVisible && videoWidth !== null) ? -

{videoWidth}x{videoHeight}

- :null - } -
-
-
- -
- ); - } - - componentDidMount() - { - const { videoTrack } = this.props; - - this._setTracks(videoTrack); - } - - componentWillUnmount() - { - clearInterval(this._videoResolutionTimer); - } - - componentWillReceiveProps(nextProps) - { - const { videoTrack } = nextProps; - - this._setTracks(videoTrack); - } - - _setTracks(videoTrack) - { - if (this._videoTrack === videoTrack) - return; - - this._videoTrack = videoTrack; - - clearInterval(this._videoResolutionTimer); - this._hideVideoResolution(); - - const { video } = this.refs; - - if (videoTrack) - { - const stream = new MediaStream; - - if (videoTrack) - stream.addTrack(videoTrack); - - video.srcObject = stream; - - if (videoTrack) - this._showVideoResolution(); - } - else - { - video.srcObject = null; - } - } - - _showVideoResolution() - { - this._videoResolutionTimer = setInterval(() => - { - const { videoWidth, videoHeight } = this.state; - const { video } = this.refs; - - // Don't re-render if nothing changed. - if (video.videoWidth === videoWidth && video.videoHeight === videoHeight) - return; - - this.setState( - { - videoWidth : video.videoWidth, - videoHeight : video.videoHeight - }); - }, 1000); - } - - _hideVideoResolution() - { - this.setState({ videoWidth: null, videoHeight: null }); - } -} - -PeerScreenView.propTypes = -{ - isMe : PropTypes.bool, - videoTrack : PropTypes.any, - videoVisible : PropTypes.bool.isRequired, - videoProfile : PropTypes.string, - videoCodec : PropTypes.string -}; diff --git a/app/lib/components/PeerView.jsx b/app/lib/components/PeerView.jsx index 840badd..e4f4572 100644 --- a/app/lib/components/PeerView.jsx +++ b/app/lib/components/PeerView.jsx @@ -14,9 +14,11 @@ export default class PeerView extends React.Component this.state = { - volume : 0, // Integer from 0 to 10., - videoWidth : null, - videoHeight : null + volume : 0, // Integer from 0 to 10., + videoWidth : null, + videoHeight : null, + screenWidth : null, + screenHeight : null }; // Latest received video track. @@ -27,6 +29,10 @@ export default class PeerView extends React.Component // @type {MediaStreamTrack} this._videoTrack = null; + // Latest received screen track. + // @type {MediaStreamTrack} + this._screenTrack = null; + // Hark instance. // @type {Object} this._hark = null; @@ -42,37 +48,60 @@ export default class PeerView extends React.Component peer, videoVisible, videoProfile, + screenVisible, + screenProfile, audioCodec, videoCodec, + screenCodec, onChangeDisplayName } = this.props; const { volume, videoWidth, - videoHeight + videoHeight, + screenWidth, + screenHeight } = this.state; return (
-
- {audioCodec ? -

{audioCodec}

- :null - } + {screenVisible ? +
+ {audioCodec ? +

{audioCodec}

+ :null + } - {videoCodec ? -

{videoCodec} {videoProfile}

- :null - } + {screenCodec ? +

{screenCodec} {screenProfile}

+ :null + } - {(videoVisible && videoWidth !== null) ? -

{videoWidth}x{videoHeight}

- :null - } -
+ {(screenVisible && screenWidth !== null) ? +

{screenWidth}x{screenHeight}

+ :null + } +
+ :
+ {audioCodec ? +

{audioCodec}

+ :null + } + + {videoCodec ? +

{videoCodec} {videoProfile}

+ :null + } + + {(videoVisible && videoWidth !== null) ? +

{videoWidth}x{videoHeight}

+ :null + } +
+ }
@@ -111,19 +140,35 @@ export default class PeerView extends React.Component