Merge branch 'develop'
|
|
@ -1,5 +1,7 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
/app/config.*
|
||||||
|
!/app/config.example.js
|
||||||
/server/config.*
|
/server/config.*
|
||||||
!/server/config.example.js
|
!/server/config.example.js
|
||||||
/server/public/
|
/server/public/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports =
|
||||||
|
{
|
||||||
|
chromeExtension : 'https://chrome.google.com/webstore/detail/fckajcjdaabdgnbdcmhhebdglogjfodi'
|
||||||
|
};
|
||||||
|
|
@ -20,6 +20,7 @@ const gulpif = require('gulp-if');
|
||||||
const gutil = require('gulp-util');
|
const gutil = require('gulp-util');
|
||||||
const plumber = require('gulp-plumber');
|
const plumber = require('gulp-plumber');
|
||||||
const rename = require('gulp-rename');
|
const rename = require('gulp-rename');
|
||||||
|
const change = require('gulp-change');
|
||||||
const header = require('gulp-header');
|
const header = require('gulp-header');
|
||||||
const touch = require('gulp-touch-cmd');
|
const touch = require('gulp-touch-cmd');
|
||||||
const browserify = require('browserify');
|
const browserify = require('browserify');
|
||||||
|
|
@ -45,6 +46,7 @@ const BANNER_OPTIONS =
|
||||||
currentYear : (new Date()).getFullYear()
|
currentYear : (new Date()).getFullYear()
|
||||||
};
|
};
|
||||||
const OUTPUT_DIR = '../server/public';
|
const OUTPUT_DIR = '../server/public';
|
||||||
|
const appOptions = require('./config');
|
||||||
|
|
||||||
// Set Node 'development' environment (unless externally set).
|
// Set Node 'development' environment (unless externally set).
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
@ -123,6 +125,11 @@ function bundle(options)
|
||||||
return rebundle();
|
return rebundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeHTML(content)
|
||||||
|
{
|
||||||
|
return content.replace(/chromeExtension/g, appOptions.chromeExtension);
|
||||||
|
}
|
||||||
|
|
||||||
gulp.task('clean', () => del(OUTPUT_DIR, { force: true }));
|
gulp.task('clean', () => del(OUTPUT_DIR, { force: true }));
|
||||||
|
|
||||||
gulp.task('lint', () =>
|
gulp.task('lint', () =>
|
||||||
|
|
@ -163,6 +170,7 @@ gulp.task('css', () =>
|
||||||
gulp.task('html', () =>
|
gulp.task('html', () =>
|
||||||
{
|
{
|
||||||
return gulp.src('index.html')
|
return gulp.src('index.html')
|
||||||
|
.pipe(change(changeHTML))
|
||||||
.pipe(gulp.dest(OUTPUT_DIR));
|
.pipe(gulp.dest(OUTPUT_DIR));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
<meta name='description' content='multiparty meeting - Cutting Edge WebRTC Video Conferencing'>
|
<meta name='description' content='multiparty meeting - Cutting Edge WebRTC Video Conferencing'>
|
||||||
|
|
||||||
<link rel='stylesheet' href='/multiparty-meeting.css'>
|
<link rel='stylesheet' href='/multiparty-meeting.css'>
|
||||||
|
<link rel="chrome-webstore-item" href="chromeExtension">
|
||||||
|
|
||||||
<script src='/resources/js/antiglobal.js'></script>
|
<script src='/resources/js/antiglobal.js'></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -15,7 +16,7 @@
|
||||||
|
|
||||||
if (window.antiglobal)
|
if (window.antiglobal)
|
||||||
{
|
{
|
||||||
window.antiglobal('___browserSync___oldSocketIo', 'io', '___browserSync___', '__core-js_shared__');
|
window.antiglobal('__multipartyMeetingScreenShareExtensionAvailable__', '___browserSync___oldSocketIo', 'io', '___browserSync___', '__core-js_shared__');
|
||||||
setInterval(window.antiglobal, 180000);
|
setInterval(window.antiglobal, 180000);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import protooClient from 'protoo-client';
|
import protooClient from 'protoo-client';
|
||||||
import * as mediasoupClient from 'mediasoup-client';
|
import * as mediasoupClient from 'mediasoup-client';
|
||||||
import Logger from './Logger';
|
import Logger from './Logger';
|
||||||
|
import ScreenShare from './ScreenShare';
|
||||||
import { getProtooUrl } from './urlFactory';
|
import { getProtooUrl } from './urlFactory';
|
||||||
import * as cookiesManager from './cookiesManager';
|
import * as cookiesManager from './cookiesManager';
|
||||||
import * as requestActions from './redux/requestActions';
|
import * as requestActions from './redux/requestActions';
|
||||||
|
|
@ -79,12 +80,15 @@ export default class RoomClient
|
||||||
// Local Webcam. Object with:
|
// Local Webcam. Object with:
|
||||||
// - {MediaDeviceInfo} [device]
|
// - {MediaDeviceInfo} [device]
|
||||||
// - {String} [resolution] - 'qvga' / 'vga' / 'hd'.
|
// - {String} [resolution] - 'qvga' / 'vga' / 'hd'.
|
||||||
this._webcam =
|
this._webcam = {
|
||||||
{
|
|
||||||
device : null,
|
device : null,
|
||||||
resolution : 'hd'
|
resolution : 'hd'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._screenSharing = ScreenShare.create();
|
||||||
|
|
||||||
|
this._screenSharingProducer = null;
|
||||||
|
|
||||||
this._join({ displayName, device });
|
this._join({ displayName, device });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,6 +193,79 @@ export default class RoomClient
|
||||||
this._micProducer.resume();
|
this._micProducer.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
installExtension()
|
||||||
|
{
|
||||||
|
logger.debug('installExtension()');
|
||||||
|
|
||||||
|
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 : this._room.canSend('video'),
|
||||||
|
needExtension : false
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('installExtension() | failed: %o', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enableScreenSharing()
|
||||||
|
{
|
||||||
|
logger.debug('enableScreenSharing()');
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
return this._setScreenShareProducer();
|
||||||
|
})
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('enableScreenSharing() | failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
enableWebcam()
|
enableWebcam()
|
||||||
{
|
{
|
||||||
logger.debug('enableWebcam()');
|
logger.debug('enableWebcam()');
|
||||||
|
|
@ -222,6 +299,30 @@ export default class RoomClient
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableScreenSharing()
|
||||||
|
{
|
||||||
|
logger.debug('disableScreenSharing()');
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(true));
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
this._screenSharingProducer.close();
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(false));
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('disableScreenSharing() | failed: %o', error);
|
||||||
|
|
||||||
|
this._dispatch(
|
||||||
|
stateActions.setScreenShareInProgress(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
disableWebcam()
|
disableWebcam()
|
||||||
{
|
{
|
||||||
logger.debug('disableWebcam()');
|
logger.debug('disableWebcam()');
|
||||||
|
|
@ -736,6 +837,12 @@ export default class RoomClient
|
||||||
canSendMic : this._room.canSend('audio'),
|
canSendMic : this._room.canSend('audio'),
|
||||||
canSendWebcam : this._room.canSend('video')
|
canSendWebcam : this._room.canSend('video')
|
||||||
}));
|
}));
|
||||||
|
this._dispatch(stateActions.setScreenCapabilities(
|
||||||
|
{
|
||||||
|
canShareScreen : this._room.canSend('video') &&
|
||||||
|
this._screenSharing.isScreenShareAvailable(),
|
||||||
|
needExtension : this._screenSharing.needExtension()
|
||||||
|
}));
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -906,6 +1013,117 @@ export default class RoomClient
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setScreenShareProducer()
|
||||||
|
{
|
||||||
|
if (!this._room.canSend('video'))
|
||||||
|
{
|
||||||
|
return Promise.reject(
|
||||||
|
new Error('cannot send screen'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let producer;
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
const available = this._screenSharing.isScreenShareAvailable() &&
|
||||||
|
!this._screenSharing.needExtension();
|
||||||
|
|
||||||
|
if (!available)
|
||||||
|
throw new Error('screen sharing not available');
|
||||||
|
|
||||||
|
logger.debug('_setScreenShareProducer() | calling getUserMedia()');
|
||||||
|
|
||||||
|
return this._screenSharing.start({
|
||||||
|
width : 1280,
|
||||||
|
height : 720,
|
||||||
|
frameRate : 3
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stream) =>
|
||||||
|
{
|
||||||
|
const track = stream.getVideoTracks()[0];
|
||||||
|
|
||||||
|
producer = this._room.createProducer(
|
||||||
|
track, { simulcast: false }, { source: 'screen' });
|
||||||
|
|
||||||
|
// No need to keep original track.
|
||||||
|
track.stop();
|
||||||
|
|
||||||
|
// Send it.
|
||||||
|
return producer.send(this._sendTransport);
|
||||||
|
})
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
this._screenSharingProducer = producer;
|
||||||
|
|
||||||
|
this._dispatch(stateActions.addProducer(
|
||||||
|
{
|
||||||
|
id : producer.id,
|
||||||
|
source : 'screen',
|
||||||
|
deviceLabel : 'screen',
|
||||||
|
type : 'screen',
|
||||||
|
locallyPaused : producer.locallyPaused,
|
||||||
|
remotelyPaused : producer.remotelyPaused,
|
||||||
|
track : producer.track,
|
||||||
|
codec : producer.rtpParameters.codecs[0].name
|
||||||
|
}));
|
||||||
|
|
||||||
|
producer.on('close', (originator) =>
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'webcam Producer "close" event [originator:%s]', originator);
|
||||||
|
|
||||||
|
this._screenSharingProducer = null;
|
||||||
|
this._dispatch(stateActions.removeProducer(producer.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.on('pause', (originator) =>
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'webcam Producer "pause" event [originator:%s]', originator);
|
||||||
|
|
||||||
|
this._dispatch(stateActions.setProducerPaused(producer.id, originator));
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.on('resume', (originator) =>
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
'webcam Producer "resume" event [originator:%s]', originator);
|
||||||
|
|
||||||
|
this._dispatch(stateActions.setProducerResumed(producer.id, originator));
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.on('handled', () =>
|
||||||
|
{
|
||||||
|
logger.debug('webcam Producer "handled" event');
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.on('unhandled', () =>
|
||||||
|
{
|
||||||
|
logger.debug('webcam Producer "unhandled" event');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() =>
|
||||||
|
{
|
||||||
|
logger.debug('_setScreenShareProducer() succeeded');
|
||||||
|
})
|
||||||
|
.catch((error) =>
|
||||||
|
{
|
||||||
|
logger.error('_setScreenShareProducer() failed:%o', error);
|
||||||
|
|
||||||
|
this._dispatch(requestActions.notify(
|
||||||
|
{
|
||||||
|
text : `Screen share producer failed: ${error.name}:${error.message}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (producer)
|
||||||
|
producer.close();
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_setWebcamProducer()
|
_setWebcamProducer()
|
||||||
{
|
{
|
||||||
if (!this._room.canSend('video'))
|
if (!this._room.canSend('video'))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
import { getBrowserType } from './utils';
|
||||||
|
|
||||||
|
class ChromeScreenShare
|
||||||
|
{
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(options = { })
|
||||||
|
{
|
||||||
|
const state = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
window.addEventListener('message', _onExtensionMessage, false);
|
||||||
|
window.postMessage({ type: 'getStreamId' }, '*');
|
||||||
|
|
||||||
|
function _onExtensionMessage({ data })
|
||||||
|
{
|
||||||
|
if (data.type !== 'gotStreamId')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const constraints = state._toConstraints(options, data.streamId);
|
||||||
|
|
||||||
|
navigator.mediaDevices.getUserMedia(constraints)
|
||||||
|
.then((stream) =>
|
||||||
|
{
|
||||||
|
window.removeEventListener('message', _onExtensionMessage);
|
||||||
|
|
||||||
|
state._stream = stream;
|
||||||
|
resolve(stream);
|
||||||
|
})
|
||||||
|
.catch((err) =>
|
||||||
|
{
|
||||||
|
window.removeEventListener('message', _onExtensionMessage);
|
||||||
|
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
if (this._stream instanceof MediaStream === false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stream.getTracks().forEach((track) => track.stop());
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isScreenShareAvailable()
|
||||||
|
{
|
||||||
|
if ('__multipartyMeetingScreenShareExtensionAvailable__' in window)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
needExtension()
|
||||||
|
{
|
||||||
|
if ('__multipartyMeetingScreenShareExtensionAvailable__' in window)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_toConstraints(options, streamId)
|
||||||
|
{
|
||||||
|
const constraints = {
|
||||||
|
video : {
|
||||||
|
mandatory : {
|
||||||
|
chromeMediaSource : 'desktop',
|
||||||
|
chromeMediaSourceId : streamId
|
||||||
|
},
|
||||||
|
optional : [ {
|
||||||
|
googTemporalLayeredScreencast : true
|
||||||
|
} ]
|
||||||
|
},
|
||||||
|
audio : false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFinite(options.width))
|
||||||
|
{
|
||||||
|
constraints.video.mandatory.maxWidth = options.width;
|
||||||
|
constraints.video.mandatory.minWidth = options.width;
|
||||||
|
}
|
||||||
|
if (isFinite(options.height))
|
||||||
|
{
|
||||||
|
constraints.video.mandatory.maxHeight = options.height;
|
||||||
|
constraints.video.mandatory.minHeight = options.height;
|
||||||
|
}
|
||||||
|
if (isFinite(options.frameRate))
|
||||||
|
{
|
||||||
|
constraints.video.mandatory.maxFrameRate = options.frameRate;
|
||||||
|
constraints.video.mandatory.minFrameRate = options.frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FirefoxScreenShare
|
||||||
|
{
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(options = {})
|
||||||
|
{
|
||||||
|
const constraints = this._toConstraints(options);
|
||||||
|
|
||||||
|
return navigator.mediaDevices.getUserMedia(constraints)
|
||||||
|
.then((stream) =>
|
||||||
|
{
|
||||||
|
this._stream = stream;
|
||||||
|
|
||||||
|
return Promise.resolve(stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
if (this._stream instanceof MediaStream === false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stream.getTracks().forEach((track) => track.stop());
|
||||||
|
this._stream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isScreenShareAvailable()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
needExtension()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_toConstraints(options)
|
||||||
|
{
|
||||||
|
const constraints = {
|
||||||
|
video : {
|
||||||
|
mediaSource : 'window'
|
||||||
|
},
|
||||||
|
audio : false
|
||||||
|
};
|
||||||
|
|
||||||
|
if ('mediaSource' in options)
|
||||||
|
{
|
||||||
|
constraints.video.mediaSource = options.mediaSource;
|
||||||
|
}
|
||||||
|
if (isFinite(options.width))
|
||||||
|
{
|
||||||
|
constraints.video.width = {
|
||||||
|
min : options.width,
|
||||||
|
max : options.width
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isFinite(options.height))
|
||||||
|
{
|
||||||
|
constraints.video.height = {
|
||||||
|
min : options.height,
|
||||||
|
max : options.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isFinite(options.frameRate))
|
||||||
|
{
|
||||||
|
constraints.video.frameRate = {
|
||||||
|
min : options.frameRate,
|
||||||
|
max : options.frameRate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ScreenShare
|
||||||
|
{
|
||||||
|
static create()
|
||||||
|
{
|
||||||
|
switch (getBrowserType())
|
||||||
|
{
|
||||||
|
case 'firefox':
|
||||||
|
{
|
||||||
|
return new FirefoxScreenShare();
|
||||||
|
}
|
||||||
|
case 'chrome':
|
||||||
|
{
|
||||||
|
return new ChromeScreenShare();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,8 @@ const Peer = (props) =>
|
||||||
const {
|
const {
|
||||||
peer,
|
peer,
|
||||||
micConsumer,
|
micConsumer,
|
||||||
webcamConsumer
|
webcamConsumer,
|
||||||
|
screenConsumer
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const micEnabled = (
|
const micEnabled = (
|
||||||
|
|
@ -23,11 +24,22 @@ const Peer = (props) =>
|
||||||
!webcamConsumer.remotelyPaused
|
!webcamConsumer.remotelyPaused
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const screenVisible = (
|
||||||
|
Boolean(screenConsumer) &&
|
||||||
|
!screenConsumer.locallyPaused &&
|
||||||
|
!screenConsumer.remotelyPaused
|
||||||
|
);
|
||||||
|
|
||||||
let videoProfile;
|
let videoProfile;
|
||||||
|
|
||||||
if (webcamConsumer)
|
if (webcamConsumer)
|
||||||
videoProfile = webcamConsumer.profile;
|
videoProfile = webcamConsumer.profile;
|
||||||
|
|
||||||
|
let screenProfile;
|
||||||
|
|
||||||
|
if (screenConsumer)
|
||||||
|
screenProfile = screenConsumer.profile;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='Peer'>
|
<div data-component='Peer'>
|
||||||
<div className='indicators'>
|
<div className='indicators'>
|
||||||
|
|
@ -52,10 +64,14 @@ const Peer = (props) =>
|
||||||
peer={peer}
|
peer={peer}
|
||||||
audioTrack={micConsumer ? micConsumer.track : null}
|
audioTrack={micConsumer ? micConsumer.track : null}
|
||||||
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
videoTrack={webcamConsumer ? webcamConsumer.track : null}
|
||||||
|
screenTrack={screenConsumer ? screenConsumer.track : null}
|
||||||
videoVisible={videoVisible}
|
videoVisible={videoVisible}
|
||||||
videoProfile={videoProfile}
|
videoProfile={videoProfile}
|
||||||
|
screenVisible={screenVisible}
|
||||||
|
screenProfile={screenProfile}
|
||||||
audioCodec={micConsumer ? micConsumer.codec : null}
|
audioCodec={micConsumer ? micConsumer.codec : null}
|
||||||
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
videoCodec={webcamConsumer ? webcamConsumer.codec : null}
|
||||||
|
screenCodec={screenConsumer ? screenConsumer.codec : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -65,7 +81,8 @@ Peer.propTypes =
|
||||||
{
|
{
|
||||||
peer : appPropTypes.Peer.isRequired,
|
peer : appPropTypes.Peer.isRequired,
|
||||||
micConsumer : appPropTypes.Consumer,
|
micConsumer : appPropTypes.Consumer,
|
||||||
webcamConsumer : appPropTypes.Consumer
|
webcamConsumer : appPropTypes.Consumer,
|
||||||
|
screenConsumer : appPropTypes.Consumer
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, { name }) =>
|
const mapStateToProps = (state, { name }) =>
|
||||||
|
|
@ -77,11 +94,14 @@ const mapStateToProps = (state, { name }) =>
|
||||||
consumersArray.find((consumer) => consumer.source === 'mic');
|
consumersArray.find((consumer) => consumer.source === 'mic');
|
||||||
const webcamConsumer =
|
const webcamConsumer =
|
||||||
consumersArray.find((consumer) => consumer.source === 'webcam');
|
consumersArray.find((consumer) => consumer.source === 'webcam');
|
||||||
|
const screenConsumer =
|
||||||
|
consumersArray.find((consumer) => consumer.source === 'screen');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peer,
|
peer,
|
||||||
micConsumer,
|
micConsumer,
|
||||||
webcamConsumer
|
webcamConsumer,
|
||||||
|
screenConsumer
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ export default class PeerView extends React.Component
|
||||||
|
|
||||||
this.state =
|
this.state =
|
||||||
{
|
{
|
||||||
volume : 0, // Integer from 0 to 10.,
|
volume : 0, // Integer from 0 to 10.,
|
||||||
videoWidth : null,
|
videoWidth : null,
|
||||||
videoHeight : null
|
videoHeight : null,
|
||||||
|
screenWidth : null,
|
||||||
|
screenHeight : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Latest received video track.
|
// Latest received video track.
|
||||||
|
|
@ -27,6 +29,10 @@ export default class PeerView extends React.Component
|
||||||
// @type {MediaStreamTrack}
|
// @type {MediaStreamTrack}
|
||||||
this._videoTrack = null;
|
this._videoTrack = null;
|
||||||
|
|
||||||
|
// Latest received screen track.
|
||||||
|
// @type {MediaStreamTrack}
|
||||||
|
this._screenTrack = null;
|
||||||
|
|
||||||
// Hark instance.
|
// Hark instance.
|
||||||
// @type {Object}
|
// @type {Object}
|
||||||
this._hark = null;
|
this._hark = null;
|
||||||
|
|
@ -42,37 +48,60 @@ export default class PeerView extends React.Component
|
||||||
peer,
|
peer,
|
||||||
videoVisible,
|
videoVisible,
|
||||||
videoProfile,
|
videoProfile,
|
||||||
|
screenVisible,
|
||||||
|
screenProfile,
|
||||||
audioCodec,
|
audioCodec,
|
||||||
videoCodec,
|
videoCodec,
|
||||||
|
screenCodec,
|
||||||
onChangeDisplayName
|
onChangeDisplayName
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
volume,
|
volume,
|
||||||
videoWidth,
|
videoWidth,
|
||||||
videoHeight
|
videoHeight,
|
||||||
|
screenWidth,
|
||||||
|
screenHeight
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component='PeerView'>
|
<div data-component='PeerView'>
|
||||||
<div className='info'>
|
<div className='info'>
|
||||||
<div className={classnames('media', { 'is-me': isMe })}>
|
<div className={classnames('media', { 'is-me': isMe })}>
|
||||||
<div className='box'>
|
{screenVisible ?
|
||||||
{audioCodec ?
|
<div className='box'>
|
||||||
<p className='codec'>{audioCodec}</p>
|
{audioCodec ?
|
||||||
:null
|
<p className='codec'>{audioCodec}</p>
|
||||||
}
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
{videoCodec ?
|
{screenCodec ?
|
||||||
<p className='codec'>{videoCodec} {videoProfile}</p>
|
<p className='codec'>{screenCodec} {screenProfile}</p>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
|
|
||||||
{(videoVisible && videoWidth !== null) ?
|
{(screenVisible && screenWidth !== null) ?
|
||||||
<p className='resolution'>{videoWidth}x{videoHeight}</p>
|
<p className='resolution'>{screenWidth}x{screenHeight}</p>
|
||||||
:null
|
:null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
:<div className='box'>
|
||||||
|
{audioCodec ?
|
||||||
|
<p className='codec'>{audioCodec}</p>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
|
{videoCodec ?
|
||||||
|
<p className='codec'>{videoCodec} {videoProfile}</p>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
|
{(videoVisible && videoWidth !== null) ?
|
||||||
|
<p className='resolution'>{videoWidth}x{videoHeight}</p>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classnames('peer', { 'is-me': isMe })}>
|
<div className={classnames('peer', { 'is-me': isMe })}>
|
||||||
|
|
@ -111,19 +140,35 @@ export default class PeerView extends React.Component
|
||||||
<video
|
<video
|
||||||
ref='video'
|
ref='video'
|
||||||
className={classnames({
|
className={classnames({
|
||||||
hidden : !videoVisible,
|
hidden : !videoVisible && !screenVisible,
|
||||||
'is-me' : isMe,
|
'is-me' : isMe,
|
||||||
loading : videoProfile === 'none'
|
loading : videoProfile === 'none' && screenProfile === 'none'
|
||||||
})}
|
})}
|
||||||
autoPlay
|
autoPlay
|
||||||
muted={isMe}
|
muted={isMe}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{screenVisible ?
|
||||||
|
<div className='minivideo'>
|
||||||
|
<video
|
||||||
|
ref='minivideo'
|
||||||
|
className={classnames({
|
||||||
|
hidden : !videoVisible,
|
||||||
|
'is-me' : isMe,
|
||||||
|
loading : videoProfile === 'none'
|
||||||
|
})}
|
||||||
|
autoPlay
|
||||||
|
muted={isMe}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
:null
|
||||||
|
}
|
||||||
|
|
||||||
<div className='volume-container'>
|
<div className='volume-container'>
|
||||||
<div className={classnames('bar', `level${volume}`)} />
|
<div className={classnames('bar', `level${volume}`)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{videoProfile === 'none' ?
|
{videoProfile === 'none' && screenProfile === 'none' ?
|
||||||
<div className='spinner-container'>
|
<div className='spinner-container'>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -135,9 +180,9 @@ export default class PeerView extends React.Component
|
||||||
|
|
||||||
componentDidMount()
|
componentDidMount()
|
||||||
{
|
{
|
||||||
const { audioTrack, videoTrack } = this.props;
|
const { audioTrack, videoTrack, screenTrack } = this.props;
|
||||||
|
|
||||||
this._setTracks(audioTrack, videoTrack);
|
this._setTracks(audioTrack, videoTrack, screenTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount()
|
componentWillUnmount()
|
||||||
|
|
@ -150,18 +195,21 @@ export default class PeerView extends React.Component
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps)
|
componentWillReceiveProps(nextProps)
|
||||||
{
|
{
|
||||||
const { audioTrack, videoTrack } = nextProps;
|
const { audioTrack, videoTrack, screenTrack } = nextProps;
|
||||||
|
|
||||||
this._setTracks(audioTrack, videoTrack);
|
this._setTracks(audioTrack, videoTrack, screenTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setTracks(audioTrack, videoTrack)
|
_setTracks(audioTrack, videoTrack, screenTrack)
|
||||||
{
|
{
|
||||||
if (this._audioTrack === audioTrack && this._videoTrack === videoTrack)
|
if (this._audioTrack === audioTrack &&
|
||||||
|
this._videoTrack === videoTrack &&
|
||||||
|
this._screenTrack === screenTrack)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._audioTrack = audioTrack;
|
this._audioTrack = audioTrack;
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
|
this._screenTrack = screenTrack;
|
||||||
|
|
||||||
if (this._hark)
|
if (this._hark)
|
||||||
this._hark.stop();
|
this._hark.stop();
|
||||||
|
|
@ -169,9 +217,9 @@ export default class PeerView extends React.Component
|
||||||
clearInterval(this._videoResolutionTimer);
|
clearInterval(this._videoResolutionTimer);
|
||||||
this._hideVideoResolution();
|
this._hideVideoResolution();
|
||||||
|
|
||||||
const { video } = this.refs;
|
const { video, minivideo } = this.refs;
|
||||||
|
|
||||||
if (audioTrack || videoTrack)
|
if (audioTrack || videoTrack || screenTrack)
|
||||||
{
|
{
|
||||||
const stream = new MediaStream;
|
const stream = new MediaStream;
|
||||||
|
|
||||||
|
|
@ -181,7 +229,19 @@ export default class PeerView extends React.Component
|
||||||
if (videoTrack)
|
if (videoTrack)
|
||||||
stream.addTrack(videoTrack);
|
stream.addTrack(videoTrack);
|
||||||
|
|
||||||
video.srcObject = stream;
|
if (screenTrack)
|
||||||
|
{
|
||||||
|
const screenStream = new MediaStream;
|
||||||
|
|
||||||
|
screenStream.addTrack(screenTrack);
|
||||||
|
|
||||||
|
video.srcObject = screenStream;
|
||||||
|
minivideo.srcObject = stream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
video.srcObject = stream;
|
||||||
|
}
|
||||||
|
|
||||||
if (audioTrack)
|
if (audioTrack)
|
||||||
this._runHark(stream);
|
this._runHark(stream);
|
||||||
|
|
@ -252,9 +312,13 @@ PeerView.propTypes =
|
||||||
[ appPropTypes.Me, appPropTypes.Peer ]).isRequired,
|
[ appPropTypes.Me, appPropTypes.Peer ]).isRequired,
|
||||||
audioTrack : PropTypes.any,
|
audioTrack : PropTypes.any,
|
||||||
videoTrack : PropTypes.any,
|
videoTrack : PropTypes.any,
|
||||||
|
screenTrack : PropTypes.any,
|
||||||
videoVisible : PropTypes.bool.isRequired,
|
videoVisible : PropTypes.bool.isRequired,
|
||||||
videoProfile : PropTypes.string,
|
videoProfile : PropTypes.string,
|
||||||
|
screenVisible : PropTypes.bool.isRequired,
|
||||||
|
screenProfile : PropTypes.string,
|
||||||
audioCodec : PropTypes.string,
|
audioCodec : PropTypes.string,
|
||||||
videoCodec : PropTypes.string,
|
videoCodec : PropTypes.string,
|
||||||
|
screenCodec : PropTypes.string,
|
||||||
onChangeDisplayName : PropTypes.func
|
onChangeDisplayName : PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,123 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as appPropTypes from './appPropTypes';
|
import * as appPropTypes from './appPropTypes';
|
||||||
|
import * as stateActions from '../redux/stateActions';
|
||||||
import { Appear } from './transitions';
|
import { Appear } from './transitions';
|
||||||
import Peer from './Peer';
|
import Peer from './Peer';
|
||||||
|
|
||||||
const Peers = ({ peers, activeSpeakerName }) =>
|
class Peers extends React.Component
|
||||||
{
|
{
|
||||||
return (
|
constructor()
|
||||||
<div data-component='Peers'>
|
{
|
||||||
|
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) =>
|
y = height / rows;
|
||||||
{
|
x = this.state.ratio * y;
|
||||||
return (
|
break;
|
||||||
<Appear key={peer.name} duration={1000}>
|
|
||||||
<div
|
|
||||||
className={classnames('peer-container', {
|
|
||||||
'active-speaker' : peer.name === activeSpeakerName
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Peer name={peer.name} />
|
|
||||||
</div>
|
|
||||||
</Appear>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</div>
|
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 (
|
||||||
|
<div data-component='Peers' ref='peers'>
|
||||||
|
{
|
||||||
|
peers.map((peer) =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Appear key={peer.name} duration={1000}>
|
||||||
|
<div
|
||||||
|
className={classnames('peer-container', {
|
||||||
|
'active-speaker' : peer.name === activeSpeakerName
|
||||||
|
})} style={style}
|
||||||
|
>
|
||||||
|
<Peer name={peer.name} />
|
||||||
|
</div>
|
||||||
|
</Appear>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Peers.propTypes =
|
Peers.propTypes =
|
||||||
{
|
{
|
||||||
peers : PropTypes.arrayOf(appPropTypes.Peer).isRequired,
|
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) =>
|
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
|
// TODO: This is not OK since it's creating a new array every time, so triggering a
|
||||||
// component rendering.
|
// component rendering.
|
||||||
const peersArray = Object.values(state.peers);
|
const peersArray = Object.values(state.peers);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peers : peersArray,
|
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;
|
export default PeersContainer;
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,40 @@ class Room extends React.Component
|
||||||
room,
|
room,
|
||||||
me,
|
me,
|
||||||
amActiveSpeaker,
|
amActiveSpeaker,
|
||||||
|
screenProducer,
|
||||||
onRoomLinkCopy,
|
onRoomLinkCopy,
|
||||||
onSetAudioMode,
|
onSetAudioMode,
|
||||||
onRestartIce,
|
onRestartIce,
|
||||||
onLeaveMeeting
|
onLeaveMeeting,
|
||||||
|
onShareScreen,
|
||||||
|
onUnShareScreen,
|
||||||
|
onNeedExtension
|
||||||
} = this.props;
|
} = 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 (
|
return (
|
||||||
<Appear duration={300}>
|
<Appear duration={300}>
|
||||||
<div data-component='Room'>
|
<div data-component='Room'>
|
||||||
|
|
@ -79,6 +107,37 @@ class Room extends React.Component
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='sidebar'>
|
<div className='sidebar'>
|
||||||
|
<div
|
||||||
|
className={classnames('button', 'screen', screenState)}
|
||||||
|
data-tip={screenTip}
|
||||||
|
data-type='dark'
|
||||||
|
onClick={() =>
|
||||||
|
{
|
||||||
|
switch (screenState)
|
||||||
|
{
|
||||||
|
case 'on':
|
||||||
|
{
|
||||||
|
onUnShareScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'off':
|
||||||
|
{
|
||||||
|
onShareScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'need-extension':
|
||||||
|
{
|
||||||
|
onNeedExtension();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classnames('button', 'audio-only', {
|
className={classnames('button', 'audio-only', {
|
||||||
on : me.audioOnly,
|
on : me.audioOnly,
|
||||||
|
|
@ -122,18 +181,27 @@ Room.propTypes =
|
||||||
room : appPropTypes.Room.isRequired,
|
room : appPropTypes.Room.isRequired,
|
||||||
me : appPropTypes.Me.isRequired,
|
me : appPropTypes.Me.isRequired,
|
||||||
amActiveSpeaker : PropTypes.bool.isRequired,
|
amActiveSpeaker : PropTypes.bool.isRequired,
|
||||||
|
screenProducer : appPropTypes.Producer,
|
||||||
onRoomLinkCopy : PropTypes.func.isRequired,
|
onRoomLinkCopy : PropTypes.func.isRequired,
|
||||||
onSetAudioMode : PropTypes.func.isRequired,
|
onSetAudioMode : PropTypes.func.isRequired,
|
||||||
onRestartIce : PropTypes.func.isRequired,
|
onRestartIce : PropTypes.func.isRequired,
|
||||||
onLeaveMeeting : PropTypes.func.isRequired
|
onLeaveMeeting : PropTypes.func.isRequired,
|
||||||
|
onShareScreen : PropTypes.func.isRequired,
|
||||||
|
onUnShareScreen : PropTypes.func.isRequired,
|
||||||
|
onNeedExtension : PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) =>
|
const mapStateToProps = (state) =>
|
||||||
{
|
{
|
||||||
|
const producersArray = Object.values(state.producers);
|
||||||
|
const screenProducer =
|
||||||
|
producersArray.find((producer) => producer.source === 'screen');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
room : state.room,
|
room : state.room,
|
||||||
me : state.me,
|
me : state.me,
|
||||||
amActiveSpeaker : state.me.name === state.room.activeSpeakerName
|
amActiveSpeaker : state.me.name === state.room.activeSpeakerName,
|
||||||
|
screenProducer : screenProducer
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -161,6 +229,18 @@ const mapDispatchToProps = (dispatch) =>
|
||||||
onLeaveMeeting : () =>
|
onLeaveMeeting : () =>
|
||||||
{
|
{
|
||||||
dispatch(requestActions.leaveRoom());
|
dispatch(requestActions.leaveRoom());
|
||||||
|
},
|
||||||
|
onShareScreen : () =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.enableScreenSharing());
|
||||||
|
},
|
||||||
|
onUnShareScreen : () =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.disableScreenSharing());
|
||||||
|
},
|
||||||
|
onNeedExtension : () =>
|
||||||
|
{
|
||||||
|
dispatch(requestActions.installExtension());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ export const Me = PropTypes.shape(
|
||||||
export const Producer = PropTypes.shape(
|
export const Producer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.number.isRequired,
|
id : PropTypes.number.isRequired,
|
||||||
source : PropTypes.oneOf([ 'mic', 'webcam' ]).isRequired,
|
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||||
deviceLabel : PropTypes.string,
|
deviceLabel : PropTypes.string,
|
||||||
type : PropTypes.oneOf([ 'front', 'back' ]),
|
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]),
|
||||||
locallyPaused : PropTypes.bool.isRequired,
|
locallyPaused : PropTypes.bool.isRequired,
|
||||||
remotelyPaused : PropTypes.bool.isRequired,
|
remotelyPaused : PropTypes.bool.isRequired,
|
||||||
track : PropTypes.any,
|
track : PropTypes.any,
|
||||||
|
|
@ -54,7 +54,7 @@ export const Consumer = PropTypes.shape(
|
||||||
{
|
{
|
||||||
id : PropTypes.number.isRequired,
|
id : PropTypes.number.isRequired,
|
||||||
peerName : PropTypes.string.isRequired,
|
peerName : PropTypes.string.isRequired,
|
||||||
source : PropTypes.oneOf([ 'mic', 'webcam' ]).isRequired,
|
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired,
|
||||||
supported : PropTypes.bool.isRequired,
|
supported : PropTypes.bool.isRequired,
|
||||||
locallyPaused : PropTypes.bool.isRequired,
|
locallyPaused : PropTypes.bool.isRequired,
|
||||||
remotelyPaused : PropTypes.bool.isRequired,
|
remotelyPaused : PropTypes.bool.isRequired,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
|
peerWidth : 200,
|
||||||
|
peerHeight : 150,
|
||||||
room :
|
room :
|
||||||
{
|
{
|
||||||
url : 'https://example.io/?&roomId=d0el8y34',
|
url : 'https://example.io/?&roomId=d0el8y34',
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
const initialState =
|
const initialState =
|
||||||
{
|
{
|
||||||
name : null,
|
name : null,
|
||||||
displayName : null,
|
displayName : null,
|
||||||
displayNameSet : false,
|
displayNameSet : false,
|
||||||
device : null,
|
device : null,
|
||||||
canSendMic : false,
|
canSendMic : false,
|
||||||
canSendWebcam : false,
|
canSendWebcam : false,
|
||||||
canChangeWebcam : false,
|
canShareScreen : false,
|
||||||
webcamInProgress : false,
|
needExtension : false,
|
||||||
audioOnly : false,
|
canChangeWebcam : false,
|
||||||
audioOnlyInProgress : false,
|
webcamInProgress : false,
|
||||||
restartIceInProgress : false
|
screenShareInProgress : false,
|
||||||
|
audioOnly : false,
|
||||||
|
audioOnlyInProgress : false,
|
||||||
|
restartIceInProgress : false
|
||||||
};
|
};
|
||||||
|
|
||||||
const me = (state = initialState, action) =>
|
const me = (state = initialState, action) =>
|
||||||
|
|
@ -26,10 +29,17 @@ const me = (state = initialState, action) =>
|
||||||
|
|
||||||
case 'SET_MEDIA_CAPABILITIES':
|
case 'SET_MEDIA_CAPABILITIES':
|
||||||
{
|
{
|
||||||
const { canSendMic, canSendWebcam } = action.payload;
|
const { canSendMic, canSendWebcam } = action.payload;
|
||||||
|
|
||||||
return { ...state, canSendMic, canSendWebcam };
|
return { ...state, canSendMic, canSendWebcam };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_SCREEN_CAPABILITIES':
|
||||||
|
{
|
||||||
|
const { canShareScreen, needExtension } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, canShareScreen, needExtension };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_CAN_CHANGE_WEBCAM':
|
case 'SET_CAN_CHANGE_WEBCAM':
|
||||||
{
|
{
|
||||||
|
|
@ -45,6 +55,13 @@ const me = (state = initialState, action) =>
|
||||||
return { ...state, webcamInProgress: flag };
|
return { ...state, webcamInProgress: flag };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_SCREEN_SHARE_IN_PROGRESS':
|
||||||
|
{
|
||||||
|
const { flag } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, screenShareInProgress: flag };
|
||||||
|
}
|
||||||
|
|
||||||
case 'SET_DISPLAY_NAME':
|
case 'SET_DISPLAY_NAME':
|
||||||
{
|
{
|
||||||
let { displayName } = action.payload;
|
let { displayName } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ const initialState =
|
||||||
{
|
{
|
||||||
url : null,
|
url : null,
|
||||||
state : 'new', // new/connecting/connected/disconnected/closed,
|
state : 'new', // new/connecting/connected/disconnected/closed,
|
||||||
activeSpeakerName : null
|
activeSpeakerName : null,
|
||||||
|
peerHeight : 300,
|
||||||
|
peerWidth : 400
|
||||||
};
|
};
|
||||||
|
|
||||||
const room = (state = initialState, action) =>
|
const room = (state = initialState, action) =>
|
||||||
|
|
@ -33,6 +35,13 @@ const room = (state = initialState, action) =>
|
||||||
return { ...state, activeSpeakerName: peerName };
|
return { ...state, activeSpeakerName: peerName };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'SET_COMPONENT_SIZE':
|
||||||
|
{
|
||||||
|
const { peerWidth, peerHeight } = action.payload;
|
||||||
|
|
||||||
|
return { ...state, peerWidth: peerWidth, peerHeight: peerHeight };
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,27 @@ export const restartIce = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 sendChatMessage = (text, name) =>
|
export const sendChatMessage = (text, name) =>
|
||||||
{
|
{
|
||||||
const message = createNewMessage(text, 'response', name);
|
const message = createNewMessage(text, 'response', name);
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,27 @@ export default ({ dispatch, getState }) => (next) =>
|
||||||
break;
|
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':
|
case 'SEND_CHAT_MESSAGE':
|
||||||
{
|
{
|
||||||
const { message } = action.payload;
|
const { message } = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -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 }) =>
|
export const setMe = ({ peerName, displayName, displayNameSet, device }) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
@ -38,6 +46,14 @@ export const setMediaCapabilities = ({ canSendMic, canSendWebcam }) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setScreenCapabilities = ({ canShareScreen, needExtension }) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_SCREEN_CAPABILITIES',
|
||||||
|
payload : { canShareScreen, needExtension }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setCanChangeWebcam = (flag) =>
|
export const setCanChangeWebcam = (flag) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
@ -126,6 +142,14 @@ export const setWebcamInProgress = (flag) =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setScreenShareInProgress = (flag) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
type : 'SET_SCREEN_SHARE_IN_PROGRESS',
|
||||||
|
payload : { flag }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const addPeer = (peer) =>
|
export const addPeer = (peer) =>
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,22 @@ export function isMobile()
|
||||||
{
|
{
|
||||||
return !mediaQueryDetectorElem.offsetParent;
|
return !mediaQueryDetectorElem.offsetParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getBrowserType()
|
||||||
|
{
|
||||||
|
const ua = navigator.userAgent.toLowerCase();
|
||||||
|
|
||||||
|
// Firefox
|
||||||
|
if (ua.indexOf('firefox') !== -1)
|
||||||
|
{
|
||||||
|
return 'firefox';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chrome
|
||||||
|
if (ua.indexOf('chrome') !== -1 && ua.indexOf('edge') === -1)
|
||||||
|
{
|
||||||
|
return 'chrome';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2570,6 +2570,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/domready/-/domready-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/domready/-/domready-1.0.8.tgz",
|
||||||
"integrity": "sha1-kfJS5Ze2Wvd+dFriTdAYXV4m1Yw="
|
"integrity": "sha1-kfJS5Ze2Wvd+dFriTdAYXV4m1Yw="
|
||||||
},
|
},
|
||||||
|
"duplexer": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"duplexer2": {
|
"duplexer2": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||||
|
|
@ -3219,6 +3225,21 @@
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"event-stream": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||||
|
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"duplexer": "0.1.1",
|
||||||
|
"from": "0.1.7",
|
||||||
|
"map-stream": "0.1.0",
|
||||||
|
"pause-stream": "0.0.11",
|
||||||
|
"split": "0.3.3",
|
||||||
|
"stream-combiner": "0.0.4",
|
||||||
|
"through": "2.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eventemitter3": {
|
"eventemitter3": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
|
||||||
|
|
@ -3773,6 +3794,12 @@
|
||||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
|
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"from": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
|
||||||
|
|
@ -5246,6 +5273,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gulp-change": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gulp-change/-/gulp-change-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-inWf4bviU0TtFk50DpkxOxXM5jk=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"event-stream": "3.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gulp-css-base64": {
|
"gulp-css-base64": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/gulp-css-base64/-/gulp-css-base64-1.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/gulp-css-base64/-/gulp-css-base64-1.3.4.tgz",
|
||||||
|
|
@ -7008,6 +7044,12 @@
|
||||||
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
|
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"map-stream": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"map-visit": {
|
"map-visit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
|
||||||
|
|
@ -8150,6 +8192,15 @@
|
||||||
"pinkie-promise": "2.0.1"
|
"pinkie-promise": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pause-stream": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||||
|
"integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"through": "2.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.0.14",
|
"version": "3.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz",
|
||||||
|
|
@ -8971,14 +9022,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/riek/-/riek-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/riek/-/riek-1.1.0.tgz",
|
||||||
"integrity": "sha1-6oVNtKTtCWIw/wQ4JQjW374pWZQ=",
|
"integrity": "sha1-6oVNtKTtCWIw/wQ4JQjW374pWZQ=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "2.6.8",
|
"debug": "2.6.9",
|
||||||
"prop-types": "15.6.0"
|
"prop-types": "15.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "2.6.8",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -9581,6 +9632,15 @@
|
||||||
"integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
|
"integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"split": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||||
|
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"through": "2.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"split-string": {
|
"split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
|
|
@ -9701,6 +9761,15 @@
|
||||||
"readable-stream": "2.3.3"
|
"readable-stream": "2.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"stream-combiner": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
|
||||||
|
"integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"duplexer": "0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stream-combiner2": {
|
"stream-combiner2": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-css-base64": "^1.3.4",
|
"gulp-css-base64": "^1.3.4",
|
||||||
"gulp-eslint": "^4.0.2",
|
"gulp-eslint": "^4.0.2",
|
||||||
|
"gulp-change": "^1.0.0",
|
||||||
"gulp-header": "^2.0.1",
|
"gulp-header": "^2.0.1",
|
||||||
"gulp-if": "^2.0.2",
|
"gulp-if": "^2.0.2",
|
||||||
"gulp-plumber": "^1.2.0",
|
"gulp-plumber": "^1.2.0",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 511.626 511.626" style="enable-background:new 0 0 511.626 511.626;" xml:space="preserve">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 511.626 511.626" style="enable-background:new 0 0 511.626 511.626;" xml:space="preserve">
|
||||||
<g>
|
<g>
|
||||||
<path d="M477.371,127.44c-22.843-28.074-53.871-50.249-93.076-66.523c-39.204-16.272-82.035-24.41-128.478-24.41 c-34.643,0-67.762,4.805-99.357,14.417c-31.595,9.611-58.812,22.602-81.653,38.97c-22.845,16.37-41.018,35.832-54.534,58.385 C6.757,170.833,0,194.484,0,219.228c0,28.549,8.61,55.3,25.837,80.234c17.227,24.931,40.778,45.871,70.664,62.811 c-2.096,7.611-4.57,14.846-7.426,21.693c-2.855,6.852-5.424,12.474-7.708,16.851c-2.286,4.377-5.376,9.233-9.281,14.562 c-3.899,5.328-6.849,9.089-8.848,11.275c-1.997,2.19-5.28,5.812-9.851,10.849c-4.565,5.048-7.517,8.329-8.848,9.855 c-0.193,0.089-0.953,0.952-2.285,2.567c-1.331,1.615-1.999,2.423-1.999,2.423l-1.713,2.566c-0.953,1.431-1.381,2.334-1.287,2.707 c0.096,0.373-0.094,1.331-0.57,2.851c-0.477,1.526-0.428,2.669,0.142,3.433v0.284c0.765,3.429,2.43,6.187,4.998,8.277 c2.568,2.092,5.474,2.95,8.708,2.563c12.375-1.522,23.223-3.606,32.548-6.276c49.87-12.758,93.649-35.782,131.334-69.097 c14.272,1.522,28.072,2.286,41.396,2.286c46.442,0,89.271-8.138,128.479-24.417c39.208-16.272,70.233-38.448,93.072-66.517 c22.843-28.062,34.263-58.663,34.263-91.781C511.626,186.108,500.207,155.509,477.371,127.44z" fill="#bababa"/>
|
<path d="M477.371,127.44c-22.843-28.074-53.871-50.249-93.076-66.523c-39.204-16.272-82.035-24.41-128.478-24.41 c-34.643,0-67.762,4.805-99.357,14.417c-31.595,9.611-58.812,22.602-81.653,38.97c-22.845,16.37-41.018,35.832-54.534,58.385 C6.757,170.833,0,194.484,0,219.228c0,28.549,8.61,55.3,25.837,80.234c17.227,24.931,40.778,45.871,70.664,62.811 c-2.096,7.611-4.57,14.846-7.426,21.693c-2.855,6.852-5.424,12.474-7.708,16.851c-2.286,4.377-5.376,9.233-9.281,14.562 c-3.899,5.328-6.849,9.089-8.848,11.275c-1.997,2.19-5.28,5.812-9.851,10.849c-4.565,5.048-7.517,8.329-8.848,9.855 c-0.193,0.089-0.953,0.952-2.285,2.567c-1.331,1.615-1.999,2.423-1.999,2.423l-1.713,2.566c-0.953,1.431-1.381,2.334-1.287,2.707 c0.096,0.373-0.094,1.331-0.57,2.851c-0.477,1.526-0.428,2.669,0.142,3.433v0.284c0.765,3.429,2.43,6.187,4.998,8.277 c2.568,2.092,5.474,2.95,8.708,2.563c12.375-1.522,23.223-3.606,32.548-6.276c49.87-12.758,93.649-35.782,131.334-69.097 c14.272,1.522,28.072,2.286,41.396,2.286c46.442,0,89.271-8.138,128.479-24.417c39.208-16.272,70.233-38.448,93.072-66.517 c22.843-28.062,34.263-58.663,34.263-91.781C511.626,186.108,500.207,155.509,477.371,127.44z" fill="#FFFFFF"/>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#000000;}
|
||||||
|
</style>
|
||||||
|
<g id="XMLID_2_">
|
||||||
|
<path id="XMLID_8_" class="st0" d="M69.1,61.3l4.6,4.6h1.8v-4.6H69.1z M70.9,56.7l0-22.9c0-2.5-2.1-4.6-4.6-4.6H37l12,12
|
||||||
|
c0.4-0.1,0.8-0.2,1.3-0.2v-4.9l9.2,8.5L55.8,48l12.7,12.7C69.9,59.9,70.9,58.4,70.9,56.7z M26,23.9L23,26.8l3.5,3.5
|
||||||
|
c-0.9,0.8-1.5,2-1.5,3.4v22.9c0,2.5,2,4.6,4.6,4.6h-9.2v4.6H62l6.2,6.2l2.9-2.9L26,23.9z M36.5,54.4c0.7-3.4,2.1-6.8,4.7-9.3
|
||||||
|
l3.6,3.6C41.4,49.6,38.7,51.4,36.5,54.4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 733 B |
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g id="XMLID_2_">
|
||||||
|
<path id="XMLID_8_" class="st0" d="M69.1,61.3l4.6,4.6h1.8v-4.6H69.1z M70.9,56.7l0-22.9c0-2.5-2.1-4.6-4.6-4.6H37l12,12
|
||||||
|
c0.4-0.1,0.8-0.2,1.3-0.2v-4.9l9.2,8.5L55.8,48l12.7,12.7C69.9,59.9,70.9,58.4,70.9,56.7z M26,23.9L23,26.8l3.5,3.5
|
||||||
|
c-0.9,0.8-1.5,2-1.5,3.4v22.9c0,2.5,2,4.6,4.6,4.6h-9.2v4.6H62l6.2,6.2l2.9-2.9L26,23.9z M36.5,54.4c0.7-3.4,2.1-6.8,4.7-9.3
|
||||||
|
l3.6,3.6C41.4,49.6,38.7,51.4,36.5,54.4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 733 B |
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#000000;}
|
||||||
|
</style>
|
||||||
|
<g id="XMLID_2_">
|
||||||
|
<path id="XMLID_6_" class="st0" d="M66.3,61.8c2.5,0,4.6-2.1,4.6-4.6l0-22.9c0-2.5-2.1-4.6-4.6-4.6H29.7c-2.5,0-4.6,2-4.6,4.6v22.9
|
||||||
|
c0,2.5,2,4.6,4.6,4.6h-9.2v4.6h55v-4.6H66.3z M50.3,53.7v-5c-6.4,0-10.6,1.9-13.8,6.2c1.3-6.1,4.8-12.2,13.8-13.5v-4.9l9.2,8.5
|
||||||
|
L50.3,53.7z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 603 B |
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 83.208 83.208" style="enable-background:new 0 0 83.208 83.208;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#D80027;" points="25.052,69.154 17.894,76.312 53.683,76.312 46.525,69.154 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#D80027;" d="M64.419,6.896c-7.831,0-14.537,4.814-17.357,11.631H0v46.525h71.577v-22.01
|
||||||
|
c6.814-2.82,11.631-9.53,11.631-17.357C83.208,15.325,74.78,6.896,64.419,6.896z M64.419,42.685c-9.373,0-17-7.627-17-17
|
||||||
|
s7.627-17,17-17s17,7.627,17,17S73.792,42.685,64.419,42.685z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<polygon style="fill:#D80027;" points="66.338,29.372 67.068,14.258 61.764,14.258 62.533,29.372 "/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#D80027;" d="M64.434,31.039c-1.764,0-3.003,1.267-3.003,3.035c0,1.732,1.199,3.035,3.003,3.035
|
||||||
|
c1.804,0,2.97-1.303,2.97-3.035C67.368,32.306,66.202,31.039,64.434,31.039z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FFFFFF;}
|
||||||
|
</style>
|
||||||
|
<g id="XMLID_2_">
|
||||||
|
<path id="XMLID_6_" class="st0" d="M66.3,61.8c2.5,0,4.6-2.1,4.6-4.6l0-22.9c0-2.5-2.1-4.6-4.6-4.6H29.7c-2.5,0-4.6,2-4.6,4.6v22.9
|
||||||
|
c0,2.5,2,4.6,4.6,4.6h-9.2v4.6h55v-4.6H66.3z M50.3,53.7v-5c-6.4,0-10.6,1.9-13.8,6.2c1.3-6.1,4.8-12.2,13.8-13.5v-4.9l9.2,8.5
|
||||||
|
L50.3,53.7z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 603 B |
|
|
@ -15,7 +15,7 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: 70%;
|
background-size: 70%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: rgba(#000, 0.5);
|
background-color: rgba(#fff, 0.3);
|
||||||
background-image: url('/resources/images/chat-icon.svg');
|
background-image: url('/resources/images/chat-icon.svg');
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition-property: opacity, background-color;
|
transition-property: opacity, background-color;
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: linear-gradient(to bottom,
|
|
||||||
rgba($backgroundTint, 0) 0%,
|
|
||||||
rgba($backgroundTint, 0) 60%,
|
|
||||||
rgba($backgroundTint, 0.1) 70%,
|
|
||||||
rgba($backgroundTint, 0.8) 100%);
|
|
||||||
|
|
||||||
> .media {
|
> .media {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
@ -195,6 +190,39 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .minivideo {
|
||||||
|
height: 15%;
|
||||||
|
width: 15%;
|
||||||
|
bottom: 1%;
|
||||||
|
right: 1%;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> video {
|
||||||
|
flex: 100 100 auto;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
user-select: none;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: .15s;
|
||||||
|
background-color: rgba(#000, 0.75);
|
||||||
|
|
||||||
|
&.is-me {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transition-duration: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
filter: blur(5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .volume-container {
|
> .volume-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0
|
top: 0
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
[data-component='Peers'] {
|
[data-component='Peers'] {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
+desktop() {
|
+desktop() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 40px 0 140px 0;
|
padding: 0px 0 0px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
@ -29,8 +30,6 @@
|
||||||
|
|
||||||
+desktop() {
|
+desktop() {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
height: 382px;
|
|
||||||
width: 450px;
|
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
border: 1px solid rgba(#fff, 0.15);
|
border: 1px solid rgba(#fff, 0.15);
|
||||||
box-shadow: 0px 5px 12px 2px rgba(#111, 0.5);
|
box-shadow: 0px 5px 12px 2px rgba(#111, 0.5);
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.screen {
|
||||||
|
&.on {
|
||||||
|
background-image: url('/resources/images/no-share-screen-black.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.off {
|
||||||
|
background-image: url('/resources/images/share-screen-white.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unsupported {
|
||||||
|
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-extension.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.leave-meeting {
|
&.leave-meeting {
|
||||||
background-image: url('/resources/images/leave-meeting.svg');
|
background-image: url('/resources/images/leave-meeting.svg');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ body {
|
||||||
#multiparty-meeting-media-query-detector {
|
#multiparty-meeting-media-query-detector {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: -1000;
|
z-index: -1000;
|
||||||
bottom: 0;
|
bottom: 1px;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
|
|
|
||||||