Skip to content

Commit

Permalink
Raise hand call action
Browse files Browse the repository at this point in the history
Added hand and hand-off icons (need better fitting ones later).
Added "raise-hand" capability.
Broadcast "raisedHand" message through signaling and update the icons
accordingly.

Signed-off-by: Vincent Petry <vincent@nextcloud.com>
  • Loading branch information
PVince81 committed Nov 10, 2020
1 parent 6cabcf5 commit 9763a73
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 2 deletions.
2 changes: 2 additions & 0 deletions css/icons.scss
Expand Up @@ -7,6 +7,8 @@
@include icon-black-white('bell-outline', 'spreed', 1);
@include icon-black-white('emoji-smile', 'spreed', 1);
@include icon-black-white('lobby', 'spreed', 1);
@include icon-black-white('hand', 'spreed', 1);
@include icon-black-white('hand-off', 'spreed', 1);
@include icon-black-white('text', 'filetypes', 1, true);

.app-talk,
Expand Down
1 change: 1 addition & 0 deletions docs/capabilities.md
Expand Up @@ -54,3 +54,4 @@ title: Capabilities

## 11.0
* `config => previews => max-gif-size` - Maximum size in bytes below which a GIF can be embedded directly in the page at render time. Bigger files will be rendered statically using the preview endpoint instead. Can be set with `occ config:app:set spreed max-gif-size --value=X` where X is the new value in bytes. Defaults to 3 MB.
* `raise-hand` - Participants can raise or lower hand, the state change is sent through signaling messages.
1 change: 1 addition & 0 deletions img/hand-off.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/hand.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/Capabilities.php
Expand Up @@ -80,6 +80,7 @@ public function getCapabilities(): array {
'chat-replies',
'circles-support',
'force-mute',
'raise-hand',
],
'config' => [
'attachments' => [
Expand Down
37 changes: 36 additions & 1 deletion src/components/CallView/shared/LocalMediaControls.vue
Expand Up @@ -17,11 +17,19 @@
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<div v-shortkey.push="['space']"
@shortkey="handleShortkey">
<div class="buttons-bar">
<button
id="raiseHand"
v-shortkey="['h']"
v-tooltip="raisedHandButtonTooltip"
:aria-label="raisedHandButtonAriaLabel"
:class="raisedHandButtonClass"
class="forced-white"
@shortkey="toggleHandRaised"
@click="toggleHandRaised" />
<div id="muteWrapper">
<button
id="mute"
Expand Down Expand Up @@ -177,6 +185,27 @@ export default {
computed: {
raisedHandButtonClass() {
return {
'icon-hand-white': this.model.attributes.raisedHand,
'icon-hand-off-white hand-disabled': !this.model.attributes.raisedHand,
}
},
raisedHandButtonAriaLabel() {
if (!this.model.attributes.raisedHand) {
return t('spreed', 'Raise hand')
}
return t('spreed', 'Lower hand')
},
raisedHandButtonTooltip() {
return {
content: this.raisedHandButtonAriaLabel,
show: false,
}
},
audioButtonClass() {
return {
'icon-audio': this.model.attributes.audioAvailable && this.model.attributes.audioEnabled,
Expand Down Expand Up @@ -503,6 +532,10 @@ export default {
}
},
toggleHandRaised() {
this.model.toggleHandRaised(!this.model.attributes.raisedHand)
},
shareScreen() {
if (!this.model.attributes.localScreen) {
this.startShareScreen('screen')
Expand Down Expand Up @@ -626,12 +659,14 @@ export default {
height: auto;
}
.buttons-bar button.hand-disabled,
.buttons-bar button.audio-disabled,
.buttons-bar button.video-disabled,
.buttons-bar button.screensharing-disabled {
opacity: .7;
}
.buttons-bar button.hand-disabled,
.buttons-bar button.audio-disabled:not(.no-audio-available),
.buttons-bar button.video-disabled:not(.no-video-available),
.buttons-bar button.screensharing-disabled {
Expand Down
18 changes: 18 additions & 0 deletions src/components/CallView/shared/VideoBottomBar.vue
Expand Up @@ -24,6 +24,15 @@
<div v-if="!isSidebar"
class="bottom-bar"
:class="{'bottom-bar--video-on' : hasShadow, 'bottom-bar--big': isBig }">
<transition name="fade">
<div
v-show="showVideoOverlay"
class="bottom-bar__statusIndicator">
<div
v-show="!connectionStateFailedNoRestart && model.attributes.raisedHand"
class="raisedHandIndicator forced-white icon-hand-white" />
</div>
</transition>
<transition name="fade">
<div v-show="showNameIndicator"
class="bottom-bar__nameIndicator"
Expand Down Expand Up @@ -246,6 +255,7 @@ export default {
font-weight: bold;
}
}
&__statusIndicator,
&__mediaIndicator {
position: relative;
background-size: 22px;
Expand All @@ -264,6 +274,7 @@ export default {
}
}
.raisedHandIndicator,
.muteIndicator,
.hideRemoteVideo,
.screensharingIndicator,
Expand Down Expand Up @@ -303,4 +314,11 @@ export default {
opacity: .8 !important;
}
.raisedHandIndicator {
display: block;
/* like buttons */
padding: 6px 12px;
}
</style>
6 changes: 6 additions & 0 deletions src/components/SettingsDialog/SettingsDialog.vue
Expand Up @@ -98,6 +98,12 @@
{{ t('spreed', 'Push to talk or push to mute') }}
</dd>
</div>
<div>
<dt><kbd>H</kbd></dt>
<dd class="shortcut-description">
{{ t('spreed', 'Raise/lower hand') }}
</dd>
</div>
</dl>
</div>
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/utils/webrtc/models/CallParticipantModel.js
Expand Up @@ -52,6 +52,7 @@ export default function CallParticipantModel(options) {
speaking: undefined,
videoAvailable: undefined,
screen: null,
raisedHand: false,
}

this._handlers = []
Expand All @@ -67,14 +68,15 @@ export default function CallParticipantModel(options) {
this._handleUnmuteBound = this._handleUnmute.bind(this)
this._handleExtendedIceConnectionStateChangeBound = this._handleExtendedIceConnectionStateChange.bind(this)
this._handleChannelMessageBound = this._handleChannelMessage.bind(this)
this._handleRaisedHandBound = this._handleRaisedHand.bind(this)

this._webRtc.on('peerStreamAdded', this._handlePeerStreamAddedBound)
this._webRtc.on('peerStreamRemoved', this._handlePeerStreamRemovedBound)
this._webRtc.on('nick', this._handleNickBound)
this._webRtc.on('mute', this._handleMuteBound)
this._webRtc.on('unmute', this._handleUnmuteBound)
this._webRtc.on('channelMessage', this._handleChannelMessageBound)

this._webRtc.on('raisedHand', this._handleRaisedHandBound)
}

CallParticipantModel.prototype = {
Expand Down Expand Up @@ -240,6 +242,14 @@ CallParticipantModel.prototype = {
}
},

_handleRaisedHand: function(data) {
if (!this.get('peer') || this.get('peer').id !== data.id) {
return
}

this.set('raisedHand', data.raised)
},

setPeer: function(peer) {
if (peer && this.get('peerId') !== peer.id) {
console.warn('Mismatch between stored peer ID and ID of given peer: ', this.get('peerId'), peer.id)
Expand Down
18 changes: 18 additions & 0 deletions src/utils/webrtc/models/LocalMediaModel.js
Expand Up @@ -36,6 +36,7 @@ export default function LocalMediaModel() {
videoEnabled: false,
localScreen: null,
token: '',
raisedHand: false,
}

this._handlers = []
Expand Down Expand Up @@ -428,4 +429,21 @@ LocalMediaModel.prototype = {
this._webRtc.stopScreenShare()
},

/**
* Toggles hand raised mode for the local participant
*
* @param {bool} raised true for raised, false for lowered
*/
toggleHandRaised: function(raised) {
if (!this._webRtc) {
throw new Error('WebRtc not initialized yet')
}

this._webRtc.sendToAll('raiseHand', { raised: raised })

// Set state locally too, as even when sending to all the sender will not
// receive the message.
this.set('raisedHand', raised)
},

}
2 changes: 2 additions & 0 deletions src/utils/webrtc/simplewebrtc/peer.js
Expand Up @@ -255,6 +255,8 @@ Peer.prototype.handleMessage = function(message) {
} else if (message.type === 'unshareScreen') {
this.parent.emit('unshareScreen', { id: message.from })
this.end()
} else if (message.type === 'raiseHand') {
this.parent.emit('raisedHand', { id: message.from, raised: message.payload.raised })
}
}

Expand Down

0 comments on commit 9763a73

Please sign in to comment.