feat(alwaysontop): Toolbar.
This commit is contained in:
parent
382b328262
commit
1782030936
|
@ -727,12 +727,12 @@ export default {
|
||||||
// so that the user can try unmute later on and add audio/video
|
// so that the user can try unmute later on and add audio/video
|
||||||
// to the conference
|
// to the conference
|
||||||
if (!tracks.find((t) => t.isAudioTrack())) {
|
if (!tracks.find((t) => t.isAudioTrack())) {
|
||||||
this.audioMuted = true;
|
this.setAudioMuteStatus(true);
|
||||||
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tracks.find((t) => t.isVideoTrack())) {
|
if (!tracks.find((t) => t.isVideoTrack())) {
|
||||||
this.videoMuted = true;
|
this.setVideoMuteStatus(true);
|
||||||
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,7 +765,7 @@ export default {
|
||||||
muteAudio(mute, showUI = true) {
|
muteAudio(mute, showUI = true) {
|
||||||
// Not ready to modify track's state yet
|
// Not ready to modify track's state yet
|
||||||
if (!this._localTracksInitialized) {
|
if (!this._localTracksInitialized) {
|
||||||
this.audioMuted = mute;
|
this.setAudioMuteStatus(mute);
|
||||||
return;
|
return;
|
||||||
} else if (localAudio && localAudio.isMuted() === mute) {
|
} else if (localAudio && localAudio.isMuted() === mute) {
|
||||||
// NO-OP
|
// NO-OP
|
||||||
|
@ -794,7 +794,7 @@ export default {
|
||||||
muteLocalAudio(mute)
|
muteLocalAudio(mute)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
maybeShowErrorDialog(error);
|
maybeShowErrorDialog(error);
|
||||||
this.audioMuted = oldMutedStatus;
|
this.setAudioMuteStatus(oldMutedStatus);
|
||||||
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -824,7 +824,7 @@ export default {
|
||||||
muteVideo(mute, showUI = true) {
|
muteVideo(mute, showUI = true) {
|
||||||
// Not ready to modify track's state yet
|
// Not ready to modify track's state yet
|
||||||
if (!this._localTracksInitialized) {
|
if (!this._localTracksInitialized) {
|
||||||
this.videoMuted = mute;
|
this.setVideoMuteStatus(mute);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (localVideo && localVideo.isMuted() === mute) {
|
} else if (localVideo && localVideo.isMuted() === mute) {
|
||||||
|
@ -863,7 +863,7 @@ export default {
|
||||||
muteLocalVideo(mute)
|
muteLocalVideo(mute)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
maybeShowErrorDialog(error);
|
maybeShowErrorDialog(error);
|
||||||
this.videoMuted = oldMutedStatus;
|
this.setVideoMuteStatus(oldMutedStatus);
|
||||||
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1220,13 +1220,13 @@ export default {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
localVideo = newStream;
|
localVideo = newStream;
|
||||||
if (newStream) {
|
if (newStream) {
|
||||||
this.videoMuted = newStream.isMuted();
|
this.setVideoMuteStatus(newStream.isMuted());
|
||||||
this.isSharingScreen = newStream.videoType === 'desktop';
|
this.isSharingScreen = newStream.videoType === 'desktop';
|
||||||
|
|
||||||
APP.UI.addLocalStream(newStream);
|
APP.UI.addLocalStream(newStream);
|
||||||
} else {
|
} else {
|
||||||
// No video is treated the same way as being video muted
|
// No video is treated the same way as being video muted
|
||||||
this.videoMuted = true;
|
this.setVideoMuteStatus(true);
|
||||||
this.isSharingScreen = false;
|
this.isSharingScreen = false;
|
||||||
}
|
}
|
||||||
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
APP.UI.setVideoMuted(this.getMyUserId(), this.videoMuted);
|
||||||
|
@ -1245,12 +1245,13 @@ export default {
|
||||||
replaceLocalTrack(localAudio, newStream, room))
|
replaceLocalTrack(localAudio, newStream, room))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
localAudio = newStream;
|
localAudio = newStream;
|
||||||
|
|
||||||
if (newStream) {
|
if (newStream) {
|
||||||
this.audioMuted = newStream.isMuted();
|
this.setAudioMuteStatus(newStream.isMuted());
|
||||||
APP.UI.addLocalStream(newStream);
|
APP.UI.addLocalStream(newStream);
|
||||||
} else {
|
} else {
|
||||||
// No audio is treated the same way as being audio muted
|
// No audio is treated the same way as being audio muted
|
||||||
this.audioMuted = true;
|
this.setAudioMuteStatus(true);
|
||||||
}
|
}
|
||||||
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
APP.UI.setAudioMuted(this.getMyUserId(), this.audioMuted);
|
||||||
});
|
});
|
||||||
|
@ -2310,6 +2311,7 @@ export default {
|
||||||
'device count: ' + audioDeviceCount);
|
'device count: ' + audioDeviceCount);
|
||||||
|
|
||||||
APP.store.dispatch(setAudioAvailable(available));
|
APP.store.dispatch(setAudioAvailable(available));
|
||||||
|
APP.API.notifyAudioAvailabilityChanged(available);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2334,6 +2336,7 @@ export default {
|
||||||
'device count: ' + videoDeviceCount);
|
'device count: ' + videoDeviceCount);
|
||||||
|
|
||||||
APP.store.dispatch(setVideoAvailable(available));
|
APP.store.dispatch(setVideoAvailable(available));
|
||||||
|
APP.API.notifyVideoAvailabilityChanged(available);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2541,5 +2544,29 @@ export default {
|
||||||
*/
|
*/
|
||||||
getDesktopSharingSourceType() {
|
getDesktopSharingSourceType() {
|
||||||
return localVideo.sourceType;
|
return localVideo.sourceType;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the video muted status.
|
||||||
|
*
|
||||||
|
* @param {boolean} muted - New muted status.
|
||||||
|
*/
|
||||||
|
setVideoMuteStatus(muted) {
|
||||||
|
if (this.videoMuted !== muted) {
|
||||||
|
this.videoMuted = muted;
|
||||||
|
APP.API.notifyVideoMutedStatusChanged(muted);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the audio muted status.
|
||||||
|
*
|
||||||
|
* @param {boolean} muted - New muted status.
|
||||||
|
*/
|
||||||
|
setAudioMuteStatus(muted) {
|
||||||
|
if (this.audioMuted !== muted) {
|
||||||
|
this.audioMuted = muted;
|
||||||
|
APP.API.notifyAudioMutedStatusChanged(muted);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
58
doc/api.md
58
doc/api.md
|
@ -25,7 +25,7 @@ Its constructor gets a number of options:
|
||||||
* **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
|
* **parentNode**: (optional) HTML DOM Element where the iframe will be added as a child.
|
||||||
* **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
|
* **configOverwrite**: (optional) JS object with overrides for options defined in [config.js].
|
||||||
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
|
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options defined in [interface_config.js].
|
||||||
* **noSsl**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
|
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
|
||||||
* **jwt**: (optional) [JWT](https://jwt.io/) token.
|
* **jwt**: (optional) [JWT](https://jwt.io/) token.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
@ -141,6 +141,20 @@ The `listener` parameter is a Function object with one argument that will be not
|
||||||
|
|
||||||
The following events are currently supported:
|
The following events are currently supported:
|
||||||
|
|
||||||
|
* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"available": available // new available status - boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"muted": muted // new muted status - boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* **incomingMessage** - Event notifications about incoming
|
* **incomingMessage** - Event notifications about incoming
|
||||||
messages. The listener will receive an object with the following structure:
|
messages. The listener will receive an object with the following structure:
|
||||||
```javascript
|
```javascript
|
||||||
|
@ -196,6 +210,20 @@ changes. The listener will receive an object with the following structure:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"available": available // new available status - boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"muted": muted // new muted status - boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
|
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
|
||||||
|
|
||||||
You can also add multiple event listeners by using `addEventListeners`.
|
You can also add multiple event listeners by using `addEventListeners`.
|
||||||
|
@ -241,6 +269,34 @@ You can get the iframe HTML element where Jitsi Meet is loaded with the followin
|
||||||
var iframe = api.getIFrame();
|
var iframe = api.getIFrame();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can check whether the audio is muted with the following API function:
|
||||||
|
```javascript
|
||||||
|
isAudioMuted().then(function(muted) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check whether the video is muted with the following API function:
|
||||||
|
```javascript
|
||||||
|
isVideoMuted().then(function(muted) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check whether the audio is available with the following API function:
|
||||||
|
```javascript
|
||||||
|
isAudioAvailable().then(function(available) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can check whether the video is available with the following API function:
|
||||||
|
```javascript
|
||||||
|
isVideoAvailable().then(function(available) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
You can remove the embedded Jitsi Meet Conference with the following API function:
|
You can remove the embedded Jitsi Meet Conference with the following API function:
|
||||||
```javascript
|
```javascript
|
||||||
api.dispose()
|
api.dispose()
|
||||||
|
|
|
@ -26,6 +26,20 @@ let initialScreenSharingState = false;
|
||||||
*/
|
*/
|
||||||
const transport = getJitsiMeetTransport();
|
const transport = getJitsiMeetTransport();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current audio availability.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
let audioAvailable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current video availability.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
let videoAvailable = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes supported commands.
|
* Initializes supported commands.
|
||||||
*
|
*
|
||||||
|
@ -58,6 +72,26 @@ function initCommands() {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
transport.on('request', ({ data, name }, callback) => {
|
||||||
|
switch (name) {
|
||||||
|
case 'is-audio-muted':
|
||||||
|
callback(APP.conference.audioMuted);
|
||||||
|
break;
|
||||||
|
case 'is-video-muted':
|
||||||
|
callback(APP.conference.videoMuted);
|
||||||
|
break;
|
||||||
|
case 'is-audio-available':
|
||||||
|
callback(audioAvailable);
|
||||||
|
break;
|
||||||
|
case 'is-video-available':
|
||||||
|
callback(videoAvailable);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,6 +299,65 @@ class API {
|
||||||
this._sendEvent({ name: 'video-ready-to-close' });
|
this._sendEvent({ name: 'video-ready-to-close' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify external application (if API is enabled) for audio muted status
|
||||||
|
* changed.
|
||||||
|
*
|
||||||
|
* @param {boolean} muted - The new muted status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyAudioMutedStatusChanged(muted) {
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'audio-mute-status-changed',
|
||||||
|
muted
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify external application (if API is enabled) for video muted status
|
||||||
|
* changed.
|
||||||
|
*
|
||||||
|
* @param {boolean} muted - The new muted status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyVideoMutedStatusChanged(muted) {
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'video-mute-status-changed',
|
||||||
|
muted
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify external application (if API is enabled) for audio availability
|
||||||
|
* changed.
|
||||||
|
*
|
||||||
|
* @param {boolean} available - True if available and false otherwise.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyAudioAvailabilityChanged(available) {
|
||||||
|
audioAvailable = available;
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'audio-availability-changed',
|
||||||
|
available
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify external application (if API is enabled) for video available
|
||||||
|
* status changed.
|
||||||
|
*
|
||||||
|
* @param {boolean} available - True if available and false otherwise.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
notifyVideoAvailabilityChanged(available) {
|
||||||
|
videoAvailable = available;
|
||||||
|
this._sendEvent({
|
||||||
|
name: 'video-availability-changed',
|
||||||
|
available
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes the allocated resources.
|
* Disposes the allocated resources.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
const ALWAYS_ON_TOP_FILENAMES = [
|
const ALWAYS_ON_TOP_FILENAMES = [
|
||||||
'css/alwaysontop.css', 'libs/alwaysontop.bundle.min.js'
|
'css/all.css', 'libs/alwaysontop.min.js'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +34,8 @@ const commands = {
|
||||||
* events expected by jitsi-meet
|
* events expected by jitsi-meet
|
||||||
*/
|
*/
|
||||||
const events = {
|
const events = {
|
||||||
|
'audio-availability-changed': 'audioAvailabilityChanged',
|
||||||
|
'audio-mute-status-changed': 'audioMuteStatusChanged',
|
||||||
'display-name-change': 'displayNameChange',
|
'display-name-change': 'displayNameChange',
|
||||||
'incoming-message': 'incomingMessage',
|
'incoming-message': 'incomingMessage',
|
||||||
'outgoing-message': 'outgoingMessage',
|
'outgoing-message': 'outgoingMessage',
|
||||||
|
@ -41,7 +43,9 @@ const events = {
|
||||||
'participant-left': 'participantLeft',
|
'participant-left': 'participantLeft',
|
||||||
'video-ready-to-close': 'readyToClose',
|
'video-ready-to-close': 'readyToClose',
|
||||||
'video-conference-joined': 'videoConferenceJoined',
|
'video-conference-joined': 'videoConferenceJoined',
|
||||||
'video-conference-left': 'videoConferenceLeft'
|
'video-conference-left': 'videoConferenceLeft',
|
||||||
|
'video-availability-changed': 'videoAvailabilityChanged',
|
||||||
|
'video-mute-status-changed': 'videoMuteStatusChanged'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -211,9 +215,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
noSSL,
|
noSSL,
|
||||||
roomName
|
roomName
|
||||||
});
|
});
|
||||||
this._baseUrl = generateURL(domain, {
|
this._baseUrl = `${noSSL ? 'http' : 'https'}://${domain}/`;
|
||||||
noSSL
|
|
||||||
});
|
|
||||||
this._createIFrame(height, width);
|
this._createIFrame(height, width);
|
||||||
this._transport = new Transport({
|
this._transport = new Transport({
|
||||||
backend: new PostMessageTransportBackend({
|
backend: new PostMessageTransportBackend({
|
||||||
|
@ -448,6 +450,30 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the audio is available.
|
||||||
|
*
|
||||||
|
* @returns {Promise} - Resolves with true if the audio available, with
|
||||||
|
* false if not and rejects on failure.
|
||||||
|
*/
|
||||||
|
isAudioAvailable() {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-audio-available'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audio mute status.
|
||||||
|
*
|
||||||
|
* @returns {Promise} - Resolves with the audio mute status and rejects on
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
isAudioMuted() {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-audio-muted'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the iframe that loads Jitsi Meet.
|
* Returns the iframe that loads Jitsi Meet.
|
||||||
*
|
*
|
||||||
|
@ -467,6 +493,30 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||||
return this._numberOfParticipants;
|
return this._numberOfParticipants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the video is available.
|
||||||
|
*
|
||||||
|
* @returns {Promise} - Resolves with true if the video available, with
|
||||||
|
* false if not and rejects on failure.
|
||||||
|
*/
|
||||||
|
isVideoAvailable() {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-video-available'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audio mute status.
|
||||||
|
*
|
||||||
|
* @returns {Promise} - Resolves with the audio mute status and rejects on
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
isVideoMuted() {
|
||||||
|
return this._transport.sendRequest({
|
||||||
|
name: 'is-video-muted'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes event listener.
|
* Removes event listener.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,297 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import StatelessToolbar from '../toolbox/components/StatelessToolbar';
|
||||||
|
import StatelessToolbarButton
|
||||||
|
from '../toolbox/components/StatelessToolbarButton';
|
||||||
|
|
||||||
|
const { api } = window.alwaysOnTop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timeout in ms for hidding the toolbar.
|
||||||
|
*/
|
||||||
|
const TOOLBAR_TIMEOUT = 4000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map with toolbar button descriptors.
|
||||||
|
*/
|
||||||
|
const toolbarButtons = {
|
||||||
|
/**
|
||||||
|
* The descriptor of the camera toolbar button.
|
||||||
|
*/
|
||||||
|
camera: {
|
||||||
|
classNames: [ 'button', 'icon-camera' ],
|
||||||
|
enabled: true,
|
||||||
|
id: 'toolbar_button_camera',
|
||||||
|
onClick() {
|
||||||
|
api.executeCommand('toggleVideo');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor of the toolbar button which hangs up the call/conference.
|
||||||
|
*/
|
||||||
|
hangup: {
|
||||||
|
classNames: [ 'button', 'icon-hangup', 'button_hangup' ],
|
||||||
|
enabled: true,
|
||||||
|
id: 'toolbar_button_hangup',
|
||||||
|
onClick() {
|
||||||
|
api.executeCommand('hangup');
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor of the microphone toolbar button.
|
||||||
|
*/
|
||||||
|
microphone: {
|
||||||
|
classNames: [ 'button', 'icon-microphone' ],
|
||||||
|
enabled: true,
|
||||||
|
id: 'toolbar_button_mute',
|
||||||
|
onClick() {
|
||||||
|
api.executeCommand('toggleAudio');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the always on top page.
|
||||||
|
*
|
||||||
|
* @class AlwaysOnTop
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
export default class AlwaysOnTop extends Component {
|
||||||
|
/**
|
||||||
|
* Initializes new AlwaysOnTop instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
visible: true,
|
||||||
|
audioMuted: false,
|
||||||
|
videoMuted: false,
|
||||||
|
audioAvailable: false,
|
||||||
|
videoAvailable: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this._hovered = false;
|
||||||
|
|
||||||
|
this._audioAvailabilityListener
|
||||||
|
= this._audioAvailabilityListener.bind(this);
|
||||||
|
this._audioMutedListener = this._audioMutedListener.bind(this);
|
||||||
|
this._mouseMove = this._mouseMove.bind(this);
|
||||||
|
this._onMouseOver = this._onMouseOver.bind(this);
|
||||||
|
this._onMouseOut = this._onMouseOut.bind(this);
|
||||||
|
this._videoAvailabilityListener
|
||||||
|
= this._videoAvailabilityListener.bind(this);
|
||||||
|
this._videoMutedListener = this._videoMutedListener.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles audio available api events.
|
||||||
|
*
|
||||||
|
* @param {{ available: boolean }} status - The new available status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_audioAvailabilityListener({ available }) {
|
||||||
|
this.setState({ audioAvailable: available });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles audio muted api events.
|
||||||
|
*
|
||||||
|
* @param {{ muted: boolean }} status - The new muted status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_audioMutedListener({ muted }) {
|
||||||
|
this.setState({ audioMuted: muted });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the toolbar after a timeout.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_hideToolbarAfterTimeout() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._hovered) {
|
||||||
|
this._hideToolbarAfterTimeout();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ visible: false });
|
||||||
|
}, TOOLBAR_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles mouse move events.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_mouseMove() {
|
||||||
|
if (!this.state.visible) {
|
||||||
|
this.setState({ visible: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar mouse over handler.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onMouseOver() {
|
||||||
|
this._hovered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar mouse out handler.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onMouseOut() {
|
||||||
|
this._hovered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles audio available api events.
|
||||||
|
*
|
||||||
|
* @param {{ available: boolean }} status - The new available status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_videoAvailabilityListener({ available }) {
|
||||||
|
this.setState({ videoAvailable: available });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles video muted api events.
|
||||||
|
*
|
||||||
|
* @param {{ muted: boolean }} status - The new muted status.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_videoMutedListener({ muted }) {
|
||||||
|
this.setState({ videoMuted: muted });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets mouse move listener and initial toolbar timeout.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
api.on('audioMuteStatusChanged', this._audioMutedListener);
|
||||||
|
api.on('videoMuteStatusChanged', this._videoMutedListener);
|
||||||
|
api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
|
||||||
|
api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
api.isAudioMuted(),
|
||||||
|
api.isVideoMuted(),
|
||||||
|
api.isAudioAvailable(),
|
||||||
|
api.isVideoAvailable()
|
||||||
|
])
|
||||||
|
.then(([
|
||||||
|
audioMuted = false,
|
||||||
|
videoMuted = false,
|
||||||
|
audioAvailable = false,
|
||||||
|
videoAvailable = false
|
||||||
|
]) =>
|
||||||
|
this.setState({
|
||||||
|
audioMuted,
|
||||||
|
videoMuted,
|
||||||
|
audioAvailable,
|
||||||
|
videoAvailable
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', this._mouseMove);
|
||||||
|
|
||||||
|
this._hideToolbarAfterTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all listeners.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
api.removeListener('audioMuteStatusChanged',
|
||||||
|
this._audioMutedListener);
|
||||||
|
api.removeListener('videoMuteStatusChanged',
|
||||||
|
this._videoMutedListener);
|
||||||
|
api.removeListener('audioAvailabilityChanged',
|
||||||
|
this._audioAvailabilityListener);
|
||||||
|
api.removeListener('videoAvailabilityChanged',
|
||||||
|
this._videoAvailabilityListener);
|
||||||
|
window.removeEventListener('mousemove', this._mouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a timeout to hide the toolbar when the toolbar is shown.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUpdate(nextProps, nextState) {
|
||||||
|
if (!this.state.visible && nextState.visible) {
|
||||||
|
this._hideToolbarAfterTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const className
|
||||||
|
= `toolbar_primary ${this.state.visible ? 'fadeIn' : 'fadeOut'}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StatelessToolbar
|
||||||
|
className = { className }
|
||||||
|
onMouseOut = { this._onMouseOut }
|
||||||
|
onMouseOver = { this._onMouseOver }>
|
||||||
|
{
|
||||||
|
Object.entries(toolbarButtons).map(([ key, button ]) => {
|
||||||
|
const { onClick } = button;
|
||||||
|
let enabled = false, toggled = false;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'microphone':
|
||||||
|
enabled = this.state.audioAvailable;
|
||||||
|
toggled = enabled ? this.state.audioMuted : true;
|
||||||
|
break;
|
||||||
|
case 'camera':
|
||||||
|
enabled = this.state.videoAvailable;
|
||||||
|
toggled = enabled ? this.state.videoMuted : true;
|
||||||
|
break;
|
||||||
|
default: // hangup button
|
||||||
|
toggled = false;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedButton = {
|
||||||
|
...button,
|
||||||
|
enabled,
|
||||||
|
toggled
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StatelessToolbarButton
|
||||||
|
button = { updatedButton }
|
||||||
|
key = { key }
|
||||||
|
onClick = { onClick } />
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</StatelessToolbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import AlwaysOnTop from './AlwaysOnTop';
|
||||||
|
|
||||||
|
// Render the main/root Component.
|
||||||
|
ReactDOM.render(
|
||||||
|
<AlwaysOnTop />,
|
||||||
|
document.getElementById('react')
|
||||||
|
);
|
|
@ -110,9 +110,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
if (jitsiTrack.isLocal()) {
|
if (jitsiTrack.isLocal()) {
|
||||||
if (isVideoTrack) {
|
if (isVideoTrack) {
|
||||||
APP.conference.videoMuted = muted;
|
APP.conference.setVideoMuteStatus(muted);
|
||||||
} else {
|
} else {
|
||||||
APP.conference.audioMuted = muted;
|
APP.conference.setAudioMuteStatus(muted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@ import React, { Component } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a toolbar in React/Web. It is a strip that contains a set of
|
* Implements a toolbar in React/Web. It is a strip that contains a set of
|
||||||
* toolbar items such as buttons. Toolbar is commonly placed inside of a
|
* toolbar items such as buttons.
|
||||||
* Toolbox.
|
|
||||||
*
|
*
|
||||||
* @class Toolbar
|
* @class StatelessToolbar
|
||||||
* @extends Component
|
* @extends Component
|
||||||
*/
|
*/
|
||||||
export default class StatelessToolbar extends Component {
|
export default class StatelessToolbar extends Component {
|
||||||
|
|
|
@ -76,24 +76,10 @@ class Toolbar extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render(): ReactElement<*> {
|
render(): ReactElement<*> {
|
||||||
const toolbarButtons = new Map();
|
|
||||||
|
|
||||||
this.props.toolbarButtons
|
|
||||||
.forEach((button, key) => {
|
|
||||||
const { onClick } = button;
|
|
||||||
|
|
||||||
toolbarButtons.set(key, {
|
|
||||||
...button,
|
|
||||||
onClick: (...args) =>
|
|
||||||
onClick && onClick(this.props.dispatch, ...args)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
...this.props,
|
className: this.props.className,
|
||||||
onMouseOut: this._onMouseOut,
|
onMouseOut: this._onMouseOut,
|
||||||
onMouseOver: this._onMouseOver,
|
onMouseOver: this._onMouseOver
|
||||||
toolbarButtons
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -150,14 +136,15 @@ class Toolbar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tooltipPosition } = this.props;
|
const { tooltipPosition } = this.props;
|
||||||
|
|
||||||
const { onClick, onMount, onUnmount } = button;
|
const { onClick, onMount, onUnmount } = button;
|
||||||
|
const onClickWithDispatch = (...args) =>
|
||||||
|
onClick && onClick(this.props.dispatch, ...args);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
button = { button }
|
button = { button }
|
||||||
key = { key }
|
key = { key }
|
||||||
onClick = { onClick }
|
onClick = { onClickWithDispatch }
|
||||||
onMount = { onMount }
|
onMount = { onMount }
|
||||||
onUnmount = { onUnmount }
|
onUnmount = { onUnmount }
|
||||||
tooltipPosition = { tooltipPosition } />
|
tooltipPosition = { tooltipPosition } />
|
||||||
|
|
|
@ -176,6 +176,9 @@ module.exports = [
|
||||||
'device_selection_popup_bundle':
|
'device_selection_popup_bundle':
|
||||||
'./react/features/device-selection/popup.js',
|
'./react/features/device-selection/popup.js',
|
||||||
|
|
||||||
|
'alwaysontop':
|
||||||
|
'./react/features/always-on-top/index.js',
|
||||||
|
|
||||||
'do_external_connect':
|
'do_external_connect':
|
||||||
'./connection_optimization/do_external_connect.js'
|
'./connection_optimization/do_external_connect.js'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue