feat(aot): Handle video not available use cases (#2242)
This commit is contained in:
parent
40df5f97d4
commit
5ffcaca649
|
@ -78,6 +78,7 @@ import {
|
|||
import { getLocationContextRoot } from './react/features/base/util';
|
||||
import { statsEmitter } from './react/features/connection-indicator';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker';
|
||||
import { appendSuffix } from './react/features/display-name';
|
||||
import { maybeOpenFeedbackDialog } from './react/features/feedback';
|
||||
import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
|
@ -1726,15 +1727,20 @@ export default {
|
|||
if (user.isHidden()) {
|
||||
return;
|
||||
}
|
||||
const displayName = user.getDisplayName();
|
||||
|
||||
APP.store.dispatch(participantJoined({
|
||||
id,
|
||||
name: user.getDisplayName(),
|
||||
name: displayName,
|
||||
role: user.getRole()
|
||||
}));
|
||||
|
||||
logger.log('USER %s connnected', id, user);
|
||||
APP.API.notifyUserJoined(id);
|
||||
APP.API.notifyUserJoined(id, {
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
|
||||
});
|
||||
APP.UI.addUser(user);
|
||||
|
||||
// check the roles for the new user and reflect them
|
||||
|
@ -1892,6 +1898,7 @@ export default {
|
|||
}
|
||||
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
|
||||
APP.API.notifyOnStageParticipantChanged(id);
|
||||
try {
|
||||
// do not try to select participant if there is none (we
|
||||
// are alone in the room), otherwise an error will be
|
||||
|
@ -1938,7 +1945,13 @@ export default {
|
|||
id,
|
||||
name: formattedDisplayName
|
||||
}));
|
||||
APP.API.notifyDisplayNameChanged(id, formattedDisplayName);
|
||||
APP.API.notifyDisplayNameChanged(id, {
|
||||
displayName: formattedDisplayName,
|
||||
formattedDisplayName:
|
||||
appendSuffix(
|
||||
formattedDisplayName
|
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)
|
||||
});
|
||||
APP.UI.changeDisplayName(id, formattedDisplayName);
|
||||
}
|
||||
);
|
||||
|
@ -2377,7 +2390,19 @@ export default {
|
|||
APP.store.dispatch(conferenceJoined(room));
|
||||
|
||||
APP.UI.mucJoined();
|
||||
APP.API.notifyConferenceJoined(APP.conference.roomName);
|
||||
const displayName = APP.settings.getDisplayName();
|
||||
|
||||
APP.API.notifyConferenceJoined(
|
||||
this.roomName,
|
||||
this._room.myUserId(),
|
||||
{
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(
|
||||
displayName,
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME),
|
||||
avatarURL: APP.UI.getAvatarUrl()
|
||||
}
|
||||
);
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
},
|
||||
|
||||
|
@ -2748,6 +2773,14 @@ export default {
|
|||
}));
|
||||
|
||||
APP.settings.setDisplayName(formattedNickname);
|
||||
APP.API.notifyDisplayNameChanged(id, {
|
||||
displayName: formattedNickname,
|
||||
formattedDisplayName:
|
||||
appendSuffix(
|
||||
formattedNickname,
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME)
|
||||
});
|
||||
|
||||
if (room) {
|
||||
room.setDisplayName(formattedNickname);
|
||||
APP.UI.changeDisplayName(id, formattedNickname);
|
||||
|
|
|
@ -250,6 +250,7 @@
|
|||
/**
|
||||
* Positions video thumbnail display name and editor.
|
||||
*/
|
||||
#alwaysOnTop .displayname,
|
||||
.videocontainer .displayname,
|
||||
.videocontainer .editdisplayname {
|
||||
display: inline-block;
|
||||
|
@ -269,6 +270,15 @@
|
|||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#alwaysOnTop .displayname {
|
||||
font-size: 15px;
|
||||
position: inherit;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions video thumbnail display name editor.
|
||||
*/
|
||||
|
@ -507,6 +517,20 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
#videoNotAvailableScreen {
|
||||
text-align: center;
|
||||
#avatarContainer {
|
||||
height: 50vh;
|
||||
display:inline-block;
|
||||
margin-top: 25vh;
|
||||
|
||||
#avatar {
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sharedVideoAvatar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
50
doc/api.md
50
doc/api.md
|
@ -140,18 +140,26 @@ The `event` parameter is a String object with the name of the event.
|
|||
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
|
||||
|
||||
The following events are currently supported:
|
||||
* **avatarChanged** - event notifications about avatar
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"id": id, // the id of the participant that changed his avatar.
|
||||
"avatarURL": avatarURL // the new avatar URL.
|
||||
}
|
||||
```
|
||||
|
||||
* **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
|
||||
"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
|
||||
"muted": muted // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -159,9 +167,9 @@ The following events are currently supported:
|
|||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"from": from, // JID of the user that sent the message
|
||||
"nick": nick, // the nickname of the user that sent the message
|
||||
"message": txt // the text of the message
|
||||
"from": from, // The id of the user that sent the message
|
||||
"nick": nick, // the nickname of the user that sent the message
|
||||
"message": txt // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -169,7 +177,7 @@ messages. The listener will receive an object with the following structure:
|
|||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"message": txt // the text of the message
|
||||
"message": txt // the text of the message
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -177,50 +185,54 @@ messages. The listener will receive an object with the following structure:
|
|||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid, // the JID of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
"id": id, // the id of the participant that changed his display name
|
||||
"displayname": displayName // the new display name
|
||||
}
|
||||
```
|
||||
|
||||
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid // the JID of the participant
|
||||
"id": id, // the id of the participant
|
||||
"displayName": displayName // the display name of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"jid": jid // the JID of the participant
|
||||
"id": id // the id of the participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room // the room name of the conference
|
||||
"roomName": room, // the room name of the conference
|
||||
"id": id, // the id of the local participant
|
||||
"displayName": displayName, // the display name of the local participant
|
||||
"avatarURL": avatarURL // the avatar URL of the local participant
|
||||
}
|
||||
```
|
||||
|
||||
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
"roomName": room // the room name of the conference
|
||||
"roomName": room // the room name of the conference
|
||||
}
|
||||
```
|
||||
|
||||
* **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
|
||||
"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
|
||||
"muted": muted // new muted status - boolean
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -264,6 +276,16 @@ You can get the number of participants in the conference with the following API
|
|||
var numberOfParticipants = api.getNumberOfParticipants();
|
||||
```
|
||||
|
||||
You can get the avatar URL of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var avatarURL = api.getAvatarURL(participantId);
|
||||
```
|
||||
|
||||
You can get the display name of a participant in the conference with the following API function:
|
||||
```javascript
|
||||
var displayName = api.getDisplayName(participantId);
|
||||
```
|
||||
|
||||
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
|
||||
```javascript
|
||||
var iframe = api.getIFrame();
|
||||
|
|
|
@ -184,6 +184,21 @@ class API {
|
|||
initCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the large video
|
||||
* visibility changed.
|
||||
*
|
||||
* @param {boolean} isHidden - True if the large video is hidden and false
|
||||
* otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyLargeVideoVisibilityChanged(isHidden: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'large-video-visibility-changed',
|
||||
isVisible: !isHidden
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event to the external application.
|
||||
*
|
||||
|
@ -238,12 +253,14 @@ class API {
|
|||
* conference.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @param {Object} props - The display name of the user.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyUserJoined(id: string) {
|
||||
notifyUserJoined(id: string, props: Object) {
|
||||
this._sendEvent({
|
||||
name: 'participant-joined',
|
||||
id
|
||||
id,
|
||||
...props
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -261,18 +278,39 @@ class API {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* avatar.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @param {string} avatarURL - The new avatar URL of the participant.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyAvatarChanged(id: string, avatarURL: string) {
|
||||
this._sendEvent({
|
||||
name: 'avatar-changed',
|
||||
avatarURL,
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* nickname.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @param {string} displayname - User nickname.
|
||||
* @param {string} formattedDisplayName - The display name shown in Jitsi
|
||||
* meet's UI for the user.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyDisplayNameChanged(id: string, displayname: string) {
|
||||
notifyDisplayNameChanged(
|
||||
id: string,
|
||||
{ displayName, formattedDisplayName }: Object) {
|
||||
this._sendEvent({
|
||||
name: 'display-name-change',
|
||||
displayname,
|
||||
displayname: displayName,
|
||||
formattedDisplayName,
|
||||
id
|
||||
});
|
||||
}
|
||||
|
@ -282,12 +320,17 @@ class API {
|
|||
* been joined.
|
||||
*
|
||||
* @param {string} roomName - The room name.
|
||||
* @param {string} id - The id of the local user.
|
||||
* @param {Object} props - The display name and avatar URL of the local
|
||||
* user.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyConferenceJoined(roomName: string) {
|
||||
notifyConferenceJoined(roomName: string, id: string, props: Object) {
|
||||
this._sendEvent({
|
||||
name: 'video-conference-joined',
|
||||
roomName
|
||||
roomName,
|
||||
id,
|
||||
...props
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -373,6 +416,20 @@ class API {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the on stage
|
||||
* participant has changed.
|
||||
*
|
||||
* @param {string} id - User id of the new on stage participant.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnStageParticipantChanged(id: string) {
|
||||
this._sendEvent({
|
||||
name: 'on-stage-participant-changed',
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
|
|
|
@ -34,6 +34,7 @@ const commands = {
|
|||
* events expected by jitsi-meet
|
||||
*/
|
||||
const events = {
|
||||
'avatar-changed': 'avatarChanged',
|
||||
'audio-availability-changed': 'audioAvailabilityChanged',
|
||||
'audio-mute-status-changed': 'audioMuteStatusChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
|
@ -224,7 +225,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
}
|
||||
})
|
||||
});
|
||||
this._numberOfParticipants = 1;
|
||||
this._isLargeVideoVisible = true;
|
||||
this._numberOfParticipants = 0;
|
||||
this._participants = {};
|
||||
this._myUserID = undefined;
|
||||
this._onStageParticipant = undefined;
|
||||
this._setupListeners();
|
||||
id++;
|
||||
}
|
||||
|
@ -278,6 +283,34 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the on stage participant.
|
||||
*
|
||||
* @returns {string} - The id of the on stage participant.
|
||||
*/
|
||||
_getOnStageParticipant() {
|
||||
return this._onStageParticipant;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for the large video element in Jitsi Meet.
|
||||
*
|
||||
* @returns {HTMLElement|undefined} - The large video.
|
||||
*/
|
||||
_getLargeVideo() {
|
||||
const iframe = this.getIFrame();
|
||||
|
||||
if (!this._isLargeVideoVisible
|
||||
|| !iframe
|
||||
|| !iframe.contentWindow
|
||||
|| !iframe.contentWindow.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
return iframe.contentWindow.document.getElementById('largeVideo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the iframe element.
|
||||
*
|
||||
|
@ -308,12 +341,58 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
* @private
|
||||
*/
|
||||
_setupListeners() {
|
||||
|
||||
this._transport.on('event', ({ name, ...data }) => {
|
||||
if (name === 'participant-joined') {
|
||||
const userID = data.id;
|
||||
|
||||
switch (name) {
|
||||
case 'video-conference-joined':
|
||||
this._myUserID = userID;
|
||||
this._participants[userID] = {
|
||||
avatarURL: data.avatarURL
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'participant-joined': {
|
||||
this._participants[userID] = this._participants[userID] || {};
|
||||
this._participants[userID].displayName = data.displayName;
|
||||
this._participants[userID].formattedDisplayName
|
||||
= data.formattedDisplayName;
|
||||
changeParticipantNumber(this, 1);
|
||||
} else if (name === 'participant-left') {
|
||||
break;
|
||||
}
|
||||
case 'participant-left':
|
||||
changeParticipantNumber(this, -1);
|
||||
delete this._participants[userID];
|
||||
break;
|
||||
case 'display-name-change': {
|
||||
const user = this._participants[userID];
|
||||
|
||||
if (user) {
|
||||
user.displayName = data.displayname;
|
||||
user.formattedDisplayName = data.formattedDisplayName;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'avatar-changed': {
|
||||
const user = this._participants[userID];
|
||||
|
||||
if (user) {
|
||||
user.avatarURL = data.avatarURL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'on-stage-participant-changed':
|
||||
this._onStageParticipant = userID;
|
||||
this.emit('largeVideoChanged');
|
||||
break;
|
||||
case 'large-video-visibility-changed':
|
||||
this._isLargeVideoVisible = data.isVisible;
|
||||
this.emit('largeVideoChanged');
|
||||
break;
|
||||
case 'video-conference-left':
|
||||
changeParticipantNumber(this, -1);
|
||||
delete this._participants[this._myUserID];
|
||||
break;
|
||||
}
|
||||
|
||||
const eventName = events[name];
|
||||
|
@ -487,6 +566,43 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the avatar URL of a participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant.
|
||||
* @returns {string} The avatar URL.
|
||||
*/
|
||||
getAvatarURL(participantId) {
|
||||
const { avatarURL } = this._participants[participantId] || {};
|
||||
|
||||
return avatarURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name of a participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant.
|
||||
* @returns {string} The display name.
|
||||
*/
|
||||
getDisplayName(participantId) {
|
||||
const { displayName } = this._participants[participantId] || {};
|
||||
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted display name of a participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant.
|
||||
* @returns {string} The formatted display name.
|
||||
*/
|
||||
_getFormattedDisplayName(participantId) {
|
||||
const { formattedDisplayName }
|
||||
= this._participants[participantId] || {};
|
||||
|
||||
return formattedDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the iframe that loads Jitsi Meet.
|
||||
*
|
||||
|
|
|
@ -801,6 +801,16 @@ function changeAvatar(id, avatarUrl) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the avatar URL for a given user.
|
||||
*
|
||||
* @param {string} id - The id of the user.
|
||||
* @returns {string} The avatar URL.
|
||||
*/
|
||||
UI.getAvatarUrl = function(id) {
|
||||
return Avatar.getAvatarUrl(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user email.
|
||||
* @param {string} id user id
|
||||
|
|
|
@ -48,6 +48,10 @@ export default {
|
|||
users[id] = {};
|
||||
}
|
||||
users[id][prop] = val;
|
||||
APP.API.notifyAvatarChanged(
|
||||
id === 'local' ? APP.conference.getMyUserId() : id,
|
||||
this.getAvatarUrl(id)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, interfaceConfig */
|
||||
/* global $, APP, interfaceConfig */
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
import LargeContainer from './LargeContainer';
|
||||
|
@ -545,6 +545,7 @@ export class VideoContainer extends LargeContainer {
|
|||
this.avatarDisplayed = show;
|
||||
|
||||
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_VISIBLE, show);
|
||||
APP.API.notifyLargeVideoVisibilityChanged(show);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -61,6 +61,9 @@ const TOOLBAR_TIMEOUT = 4000;
|
|||
type State = {
|
||||
audioAvailable: boolean,
|
||||
audioMuted: boolean,
|
||||
avatarURL: string,
|
||||
displayName: string,
|
||||
isVideoDisplayed: boolean,
|
||||
videoAvailable: boolean,
|
||||
videoMuted: boolean,
|
||||
visible: boolean
|
||||
|
@ -89,13 +92,21 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
audioAvailable: false,
|
||||
videoAvailable: false
|
||||
videoAvailable: false,
|
||||
displayName: '',
|
||||
isVideoDisplayed: true,
|
||||
avatarURL: ''
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._audioAvailabilityListener
|
||||
= this._audioAvailabilityListener.bind(this);
|
||||
this._audioMutedListener = this._audioMutedListener.bind(this);
|
||||
this._avatarChangedListener = this._avatarChangedListener.bind(this);
|
||||
this._largeVideoChangedListener
|
||||
= this._largeVideoChangedListener.bind(this);
|
||||
this._displayNameChangedListener
|
||||
= this._displayNameChangedListener.bind(this);
|
||||
this._mouseMove = this._mouseMove.bind(this);
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onMouseOver = this._onMouseOver.bind(this);
|
||||
|
@ -128,6 +139,40 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this.setState({ audioMuted: muted });
|
||||
}
|
||||
|
||||
_avatarChangedListener: () => void;
|
||||
|
||||
/**
|
||||
* Handles avatar changed api events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_avatarChangedListener({ avatarURL, id }) {
|
||||
if (api._getOnStageParticipant() !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (avatarURL !== this.state.avatarURL) {
|
||||
this.setState({ avatarURL });
|
||||
}
|
||||
}
|
||||
|
||||
_displayNameChangedListener: () => void;
|
||||
|
||||
/**
|
||||
* Handles display name changed api events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_displayNameChangedListener({ formattedDisplayName, id }) {
|
||||
if (api._getOnStageParticipant() !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (formattedDisplayName !== this.state.displayName) {
|
||||
this.setState({ displayName: formattedDisplayName });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the toolbar after a timeout.
|
||||
*
|
||||
|
@ -144,6 +189,26 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
}, TOOLBAR_TIMEOUT);
|
||||
}
|
||||
|
||||
_largeVideoChangedListener: () => void;
|
||||
|
||||
/**
|
||||
* Handles large video changed api events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_largeVideoChangedListener() {
|
||||
const userID = api._getOnStageParticipant();
|
||||
const displayName = api._getFormattedDisplayName(userID);
|
||||
const avatarURL = api.getAvatarURL(userID);
|
||||
const isVideoDisplayed = Boolean(api._getLargeVideo());
|
||||
|
||||
this.setState({
|
||||
avatarURL,
|
||||
displayName,
|
||||
isVideoDisplayed
|
||||
});
|
||||
}
|
||||
|
||||
_mouseMove: () => void;
|
||||
|
||||
/**
|
||||
|
@ -181,6 +246,38 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
|
||||
_videoAvailabilityListener: ({ available: boolean }) => void;
|
||||
|
||||
/**
|
||||
* Renders display name and avatar for the on stage participant.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderVideoNotAvailableScreen() {
|
||||
const { avatarURL, displayName, isVideoDisplayed } = this.state;
|
||||
|
||||
if (isVideoDisplayed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id = 'videoNotAvailableScreen'>
|
||||
{
|
||||
avatarURL
|
||||
? <div id = 'avatarContainer'>
|
||||
<img
|
||||
id = 'avatar'
|
||||
src = { avatarURL } />
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<div
|
||||
className = 'displayname'
|
||||
id = 'displayname'>
|
||||
{ displayName }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles audio available api events.
|
||||
*
|
||||
|
@ -214,6 +311,11 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
api.on('videoMuteStatusChanged', this._videoMutedListener);
|
||||
api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
|
||||
api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
|
||||
api.on('largeVideoChanged', this._largeVideoChangedListener);
|
||||
api.on('displayNameChange', this._displayNameChangedListener);
|
||||
api.on('avatarChanged', this._avatarChangedListener);
|
||||
|
||||
this._largeVideoChangedListener();
|
||||
|
||||
Promise.all([
|
||||
api.isAudioMuted(),
|
||||
|
@ -256,6 +358,11 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this._audioAvailabilityListener);
|
||||
api.removeListener('videoAvailabilityChanged',
|
||||
this._videoAvailabilityListener);
|
||||
api.removeListener('largeVideoChanged',
|
||||
this._largeVideoChangedListener);
|
||||
api.removeListener('displayNameChange',
|
||||
this._displayNameChangedListener);
|
||||
api.removeListener('avatarChanged', this._avatarChangedListener);
|
||||
window.removeEventListener('mousemove', this._mouseMove);
|
||||
}
|
||||
|
||||
|
@ -283,50 +390,61 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this.state.visible ? 'fadeIn' : 'fadeOut'}`;
|
||||
|
||||
return (
|
||||
<StatelessToolbar
|
||||
className = { className }
|
||||
onMouseOut = { this._onMouseOut }
|
||||
onMouseOver = { this._onMouseOver }>
|
||||
<div id = 'alwaysOnTop'>
|
||||
<StatelessToolbar
|
||||
className = { className }
|
||||
onMouseOut = { this._onMouseOut }
|
||||
onMouseOver = { this._onMouseOver }>
|
||||
{
|
||||
Object.entries(TOOLBAR_BUTTONS).map(
|
||||
([ key, button ]) => {
|
||||
// XXX The following silences a couple of flow
|
||||
// errors:
|
||||
if (button === null
|
||||
|| typeof button !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { onClick } = button;
|
||||
let enabled = false;
|
||||
let 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>
|
||||
{
|
||||
Object.entries(TOOLBAR_BUTTONS).map(([ key, button ]) => {
|
||||
// XXX The following silences a couple of flow errors:
|
||||
if (button === null || typeof button !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { onClick } = button;
|
||||
let enabled = false;
|
||||
let 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 } />
|
||||
);
|
||||
})
|
||||
this._renderVideoNotAvailableScreen()
|
||||
}
|
||||
</StatelessToolbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { appendSuffix } from '../functions';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { participantDisplayNameChanged } from '../../base/participants';
|
||||
|
||||
|
@ -144,15 +146,12 @@ class DisplayName extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
const suffix
|
||||
= displayName && displayNameSuffix ? ` (${displayNameSuffix})` : '';
|
||||
|
||||
return (
|
||||
<span
|
||||
className = 'displayname'
|
||||
id = { elementID }
|
||||
onClick = { this._onStartEditing }>
|
||||
{ `${displayName || displayNameSuffix || ''}${suffix}` }
|
||||
{ `${appendSuffix(displayName, displayNameSuffix)}` }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Appends a suffix to the display name.
|
||||
*
|
||||
* @param {string} displayName - The display name.
|
||||
* @param {string} suffix - Suffix that will be appended.
|
||||
* @returns {string} The formatted display name.
|
||||
*/
|
||||
export function appendSuffix(displayName, suffix) {
|
||||
return `${displayName || suffix || ''}${
|
||||
displayName && suffix ? ` (${suffix})` : ''}`;
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export * from './actions';
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
|
|
Loading…
Reference in New Issue