diff --git a/conference.js b/conference.js index 82537e97c..b233261a2 100644 --- a/conference.js +++ b/conference.js @@ -341,6 +341,14 @@ export default { videoMuted: false, isSharingScreen: false, isDesktopSharingEnabled: false, + /* + * Whether the "raisedHand" flag is on. + */ + isHandRaised: false, + /* + * Whether the local participant is the dominant speaker in the conference. + */ + isDominantSpeaker: false, /** * Open new connection and join to the conference. * @param {object} options @@ -1199,6 +1207,16 @@ export default { APP.UI.handleLastNEndpoints(ids, enteringIds); }); room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => { + if (this.isLocalId(id)) { + this.isDominantSpeaker = true; + this.setRaisedHand(false); + } else { + this.isDominantSpeaker = false; + var participant = room.getParticipantById(id); + if (participant) { + APP.UI.setRaisedHandStatus(participant, false); + } + } APP.UI.markDominantSpeaker(id); }); @@ -1221,6 +1239,11 @@ export default { APP.UI.changeDisplayName(id, displayName); }); + room.on(ConferenceEvents.RAISED_HAND_STATUS_CHANGED, + (participant, raisedHandStatus) => { + APP.UI.setRaisedHandStatus(participant, raisedHandStatus); + }); + room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => { console.log("Received recorder status change: ", status, error); APP.UI.updateRecordingState(status); @@ -1525,5 +1548,29 @@ export default { */ addConferenceListener(eventName, callBack) { room.on(eventName, callBack); + }, + + /** + * Toggle the "raised hand" status, if the current state allows toggling + */ + maybeToggleRaisedHand() { + // If we are the dominant speaker, we don't enable "raise hand". + if (this.isHandRaised || !this.dominantSpeaker) { + this.setRaisedHand(!this.isHandRaised); + } + }, + + /** + * Sets the "raised hand" status to a particular value. + */ + setRaisedHand(raisedHand) { + if (raisedHand !== this.isHandRaised) + { + this.isHandRaised = raisedHand; + // Advertise the updated status + room.setRaisedHand(raisedHand); + // Update the view + APP.UI.setLocalRaisedHandStatus(raisedHand); + } } }; diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 4767ec059..30d6bef69 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -310,7 +310,7 @@ z-index: 3; } -.videocontainer>span.dominantspeakerindicator { +.videocontainer>span.indicator { bottom: 0px; left: 0px; width: 25px; @@ -327,7 +327,7 @@ border: 0px; } -#speakerindicatoricon { +#indicatoricon { padding-top: 5px; } @@ -522,4 +522,4 @@ } .hidden { -} \ No newline at end of file +} diff --git a/lang/main.json b/lang/main.json index f29084d0e..824cc6231 100644 --- a/lang/main.json +++ b/lang/main.json @@ -8,6 +8,7 @@ "participant": "Participant", "me": "me", "speaker": "Speaker", + "raisedHand": "Would like to speak", "defaultNickname": "ex. Jane Pink", "defaultLink": "e.g. __url__", "welcomepage":{ @@ -139,7 +140,8 @@ "grantedTo": "Moderator rights granted to __to__!", "grantedToUnknown": "Moderator rights granted to $t(somebody)!", "muted": "You have started the conversation muted.", - "mutedTitle": "You're muted!" + "mutedTitle": "You're muted!", + "raisedHand": "Would like to speak." }, "dialog": { "kickMessage": "Ouch! You have been kicked out of the meet!", diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 7f3d6a788..ad550d536 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -254,7 +254,25 @@ UI.changeDisplayName = function (id, displayName) { }; /** - * Intitialize conference UI. + * Sets the "raised hand" status for a participant. + */ +UI.setRaisedHandStatus = (participant, raisedHandStatus) => { + VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus); + if (raisedHandStatus) { + messageHandler.notify(participant.getDisplayName(), 'notify.somebody', + 'connected', 'notify.raisedHand'); + } +}; + +/** + * Sets the local "raised hand" status. + */ +UI.setLocalRaisedHandStatus = (raisedHandStatus) => { + VideoLayout.setRaisedHandStatus(APP.conference.localId, raisedHandStatus); +}; + +/** + * Initialize conference UI. */ UI.initConference = function () { let id = APP.conference.localId; diff --git a/modules/UI/util/MessageHandler.js b/modules/UI/util/MessageHandler.js index fdf274481..b450361f2 100644 --- a/modules/UI/util/MessageHandler.js +++ b/modules/UI/util/MessageHandler.js @@ -215,16 +215,19 @@ var messageHandler = { }, /** - * Displayes notification. - * @param displayName display name of the participant that is associated with the notification. - * @param displayNameKey the key from the language file for the display name. + * Displays a notification. + * @param displayName the display name of the participant that is + * associated with the notification. + * @param displayNameKey the key from the language file for the display + * name. Only used if displayName i not provided. * @param cls css class for the notification - * @param messageKey the key from the language file for the text of the message. + * @param messageKey the key from the language file for the text of the + * message. * @param messageArguments object with the arguments for the message. * @param options object with language options. */ - notify: function(displayName, displayNameKey, - cls, messageKey, messageArguments, options) { + notify: function(displayName, displayNameKey, cls, messageKey, + messageArguments, options) { if(!notificationsEnabled) return; diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index a750e1156..37a5d1c2a 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -422,37 +422,69 @@ SmallVideo.prototype.avatarChanged = function (avatarUrl) { }; /** - * Updates the Indicator for dominant speaker. - * - * @param isSpeaker indicates the current indicator state + * Shows or hides the dominant speaker indicator. + * @param show whether to show or hide. */ -SmallVideo.prototype.updateDominantSpeakerIndicator = function (isSpeaker) { - +SmallVideo.prototype.showDominantSpeakerIndicator = function (show) { if (!this.container) { console.warn( "Unable to set dominant speaker indicator - " + this.videoSpanId + " does not exist"); return; } - var indicatorSpan - = $('#' + this.videoSpanId + '>span.dominantspeakerindicator'); + var indicatorSpanId = "dominantspeakerindicator"; + var indicatorSpan = this.getIndicatorSpan(indicatorSpanId); - // If we do not have an indicator for this video. - if (indicatorSpan.length <= 0) { - indicatorSpan = document.createElement('span'); + indicatorSpan.innerHTML + = ""; + // adds a tooltip + UIUtil.setTooltip(indicatorSpan, "speaker", "left"); + APP.translation.translateElement($(indicatorSpan)); - indicatorSpan.innerHTML - = ""; - indicatorSpan.className = 'dominantspeakerindicator'; + $(indicatorSpan).css("visibility", show ? "visible" : "hidden"); +}; - $('#' + this.videoSpanId)[0].appendChild(indicatorSpan); - - // adds a tooltip - UIUtil.setTooltip(indicatorSpan, "speaker", "left"); - APP.translation.translateElement($(indicatorSpan)); +/** + * Shows or hides the raised hand indicator. + * @param show whether to show or hide. + */ +SmallVideo.prototype.showRaisedHandIndicator = function (show) { + if (!this.container) { + console.warn( "Unable to raised hand indication - " + + this.videoSpanId + " does not exist"); + return; } - $(indicatorSpan).css("visibility", isSpeaker ? "visible" : "hidden"); + var indicatorSpanId = "raisehandindicator"; + var indicatorSpan = this.getIndicatorSpan(indicatorSpanId); + + indicatorSpan.style.background = "#D6D61E"; + indicatorSpan.innerHTML + = ""; + + // adds a tooltip + UIUtil.setTooltip(indicatorSpan, "raisedHand", "left"); + APP.translation.translateElement($(indicatorSpan)); + + $(indicatorSpan).css("visibility", show ? "visible" : "hidden"); +}; + +/** + * Gets (creating if necessary) the "indicator" span for this SmallVideo + identified by an ID. + */ +SmallVideo.prototype.getIndicatorSpan = function(id) { + var indicatorSpan; + var spans = $(`#${this.videoSpanId}>[id=${id}`); + if (spans.length <= 0) { + indicatorSpan = document.createElement('span'); + indicatorSpan.id = id; + indicatorSpan.className = "indicator"; + $('#' + this.videoSpanId)[0].appendChild(indicatorSpan); + } else { + indicatorSpan = spans[0]; + } + return indicatorSpan; }; export default SmallVideo; diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 5f2a785b4..2be954e1b 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -562,6 +562,19 @@ var VideoLayout = { } }, + /** + * Sets the "raised hand" status for a participant identified by 'id'. + */ + setRaisedHandStatus(id, raisedHandStatus) { + var video + = APP.conference.isLocalId(id) + ? localVideoThumbnail : remoteVideos[id]; + if (video) { + video.showRaisedHandIndicator(raisedHandStatus); + } + // TODO: also display a message? + }, + /** * On dominant speaker changed event. */ @@ -576,10 +589,11 @@ var VideoLayout = { if (APP.conference.isLocalId(id)) { if(oldSpeakerRemoteVideo) { - oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false); - localVideoThumbnail.updateDominantSpeakerIndicator(true); + oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false); + // XXX Why do we not set this to id? currentDominantSpeaker = null; } + localVideoThumbnail.showDominantSpeakerIndicator(true); return; } @@ -589,12 +603,12 @@ var VideoLayout = { } // Update the current dominant speaker. - remoteVideo.updateDominantSpeakerIndicator(true); - localVideoThumbnail.updateDominantSpeakerIndicator(false); + remoteVideo.showDominantSpeakerIndicator(true); + localVideoThumbnail.showDominantSpeakerIndicator(false); // let's remove the indications from the remote video if any if (oldSpeakerRemoteVideo) { - oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false); + oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false); } currentDominantSpeaker = id; diff --git a/modules/keyboardshortcut/keyboardshortcut.js b/modules/keyboardshortcut/keyboardshortcut.js index 4f48af97d..5e623c9dd 100644 --- a/modules/keyboardshortcut/keyboardshortcut.js +++ b/modules/keyboardshortcut/keyboardshortcut.js @@ -31,6 +31,13 @@ function initShortcutHandlers() { APP.conference.toggleAudioMuted(); } }, + 82: { + character: "R", + function: function() { + APP.conference.maybeToggleRaisedHand(); + } + + }, 84: { character: "T", function: function() {