[IMP] mail: make the mute and deafen button behavior more intuitive

* When a user is already muted when setting their state as deafened
the mute state should remain after the deafen state is removed.
* When a user clicks the microphone (mute/unmute) button while being in
the deaf state, the user should end up undeafen and unmute.

task-2720026

closes odoo/odoo#82197

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Thanh Dodeur
2022-01-06 11:45:20 +00:00
parent f9a460d814
commit 1ab3d53588
6 changed files with 100 additions and 48 deletions
@@ -49,7 +49,7 @@
</t>
</span>
<div class="o_RtcCallParticipantCard_overlayTop">
<t t-if="callParticipantCard.rtcSession.isSelfMuted">
<t t-if="callParticipantCard.rtcSession.isSelfMuted and !callParticipantCard.rtcSession.isDeaf">
<span class="o_RtcCallParticipantCard_overlayTopElement" t-att-class="{'o-isMinimized': callParticipantCard.isMinimized }" title="muted" aria-label="muted">
<i class="fa fa-microphone-slash"/>
</span>
@@ -6,19 +6,19 @@
<t t-if="rtcController and messaging">
<div class="o_RtcController_buttons">
<t t-if="rtcController.callViewer.threadView.thread.rtc and messaging.rtc.currentRtcSession">
<t t-if="messaging.rtc.currentRtcSession.isSelfMuted"><t t-set="microphoneTitle">Unmute</t></t>
<t t-if="messaging.rtc.currentRtcSession.isMute"><t t-set="microphoneTitle">Unmute</t></t>
<t t-else=""><t t-set="microphoneTitle">Mute</t></t>
<button class="o_RtcController_button"
t-att-class="{ 'o-isActive': !messaging.rtc.currentRtcSession.isSelfMuted, 'o-isSmall': rtcController.isSmall }"
t-att-class="{ 'o-isActive': !messaging.rtc.currentRtcSession.isMute, 'o-isSmall': rtcController.isSmall }"
t-att-aria-label="microphoneTitle"
t-att-title="microphoneTitle"
t-on-click="rtcController.onClickMicrophone">
<div class="o_RtcController_buttonIconWrapper" t-att-class="{ 'o-isSmall': rtcController.isSmall }">
<i class="fa" t-att-class="{
'fa-lg': !rtcController.isSmall,
'fa-microphone': !messaging.rtc.currentRtcSession.isSelfMuted,
'fa-microphone-slash': messaging.rtc.currentRtcSession.isSelfMuted,
'text-danger': messaging.rtc.currentRtcSession.isSelfMuted,
'fa-microphone': !messaging.rtc.currentRtcSession.isMute,
'fa-microphone-slash': messaging.rtc.currentRtcSession.isMute,
'text-danger': messaging.rtc.currentRtcSession.isMute,
}"/>
</div>
</button>
+60 -22
View File
@@ -98,6 +98,9 @@ registerModel({
},
},
recordMethods: {
async deafen() {
await this._setDeafState(true);
},
/**
* Removes and disconnects all the peerConnections that are not current members of the call.
*
@@ -198,6 +201,9 @@ registerModel({
await this._toggleVideoBroadcast({ type: videoType });
}
},
async mute() {
await this._setMuteState(true);
},
/**
* Resets the state of the model and cleanly ends all connections and
* streams.
@@ -234,18 +240,13 @@ registerModel({
/**
* Mutes and unmutes the microphone, will not unmute if deaf.
*
* @param {Object} [param0]
* @param {string} [param0.requestAudioDevice] true if requesting the audio input device
* from the user
*/
async toggleMicrophone({ requestAudioDevice = true } = {}) {
const shouldMute = this.currentRtcSession.isDeaf || !this.currentRtcSession.isSelfMuted;
this.currentRtcSession.updateAndBroadcast({ isSelfMuted: shouldMute || !this.audioTrack });
if (!this.audioTrack && !shouldMute && requestAudioDevice) {
// if we don't have an audioTrack, we try to request it again
await this.updateLocalAudioTrack(true);
async toggleMicrophone() {
if (this.currentRtcSession.isMute) {
await this.unmute();
} else {
await this.mute();
}
await this.async(() => this._updateLocalAudioTrackEnabledState());
},
/**
* toggles screen broadcasting to peers.
@@ -259,6 +260,17 @@ registerModel({
async toggleUserVideo() {
this._toggleVideoBroadcast({ type: 'user-video' });
},
async undeafen() {
await this._setDeafState(false);
},
async unmute() {
if (this.audioTrack) {
await this._setMuteState(false);
} else {
// if we don't have an audioTrack, we try to request it again
await this.updateLocalAudioTrack(true);
}
},
/**
* @param {Boolean} audio
*/
@@ -301,7 +313,7 @@ registerModel({
await this.async(() => this._updateLocalAudioTrackEnabledState());
});
this.currentRtcSession.updateAndBroadcast({ isSelfMuted: false });
audioTrack.enabled = !this.currentRtcSession.isSelfMuted && this.currentRtcSession.isTalking;
audioTrack.enabled = !this.currentRtcSession.isMute && this.currentRtcSession.isTalking;
this.update({ audioTrack });
await this.async(() => this.updateVoiceActivation());
for (const [token, peerConnection] of Object.entries(this._peerConnections)) {
@@ -485,7 +497,10 @@ registerModel({
type: 'peerToPeer',
payload: {
type: 'audio',
state: { isTalking: this.currentRtcSession.isTalking, isSelfMuted: this.currentRtcSession.isSelfMuted },
state: {
isTalking: this.audioTrack && this.audioTrack.enabled,
isSelfMuted: this.currentRtcSession.isSelfMuted,
},
},
});
} catch (e) {
@@ -836,6 +851,26 @@ registerModel({
];
return JSON.stringify(Object.fromEntries(toLog.map(p => [p, dataChannel[p]])));
},
/**
* @param {Boolean} isDeaf
*/
async _setDeafState(isDeaf) {
this.currentRtcSession.updateAndBroadcast({ isDeaf });
for (const session of this.messaging.models['RtcSession'].all()) {
if (!session.audioElement) {
continue;
}
session.audioElement.muted = isDeaf;
}
await this._updateLocalAudioTrackEnabledState();
},
/**
* @param {Boolean} isSelfMuted
*/
async _setMuteState(isSelfMuted) {
this.currentRtcSession.updateAndBroadcast({ isSelfMuted });
await this._updateLocalAudioTrackEnabledState();
},
/**
* Updates the "isTalking" state of the current user and sets the
* enabled state of its audio track accordingly.
@@ -851,7 +886,7 @@ registerModel({
return;
}
this.currentRtcSession.update({ isTalking });
if (!this.currentRtcSession.isSelfMuted) {
if (!this.currentRtcSession.isMute) {
await this._updateLocalAudioTrackEnabledState();
}
},
@@ -928,8 +963,8 @@ registerModel({
}
},
/**
* Sets the enabled property of the local audio track based on the
* current session state. And notifies peers of the new audio state.
* Sets the enabled property of the local audio track and notifies
* peers of the new state.
*
* @private
*/
@@ -937,14 +972,14 @@ registerModel({
if (!this.audioTrack) {
return;
}
this.audioTrack.enabled = !this.currentRtcSession.isSelfMuted && this.currentRtcSession.isTalking;
this.audioTrack.enabled = !this.currentRtcSession.isMute && this.currentRtcSession.isTalking;
await this._notifyPeers(Object.keys(this._peerConnections), {
event: 'trackChange',
type: 'peerToPeer',
payload: {
type: 'audio',
state: {
isTalking: this.currentRtcSession.isTalking && !this.currentRtcSession.isSelfMuted,
isTalking: this.audioTrack.enabled,
isSelfMuted: this.currentRtcSession.isSelfMuted,
isDeaf: this.currentRtcSession.isDeaf,
},
@@ -1119,19 +1154,22 @@ registerModel({
if (!this.channel) {
return;
}
if (this.messaging.userSetting.rtcConfigurationMenu.isRegisteringKey) {
if (!this.messaging.userSetting.usePushToTalk || !this.messaging.userSetting.isPushToTalkKey(ev)) {
return;
}
if (!this.messaging.userSetting.usePushToTalk || !this.messaging.userSetting.isPushToTalkKey(ev)) {
if (this.currentRtcSession.isMute) {
return;
}
if (this.messaging.userSetting.rtcConfigurationMenu.isRegisteringKey) {
return;
}
if (this._pushToTalkTimeoutId) {
browser.clearTimeout(this._pushToTalkTimeoutId);
}
if (!this.currentRtcSession.isTalking && !this.currentRtcSession.isSelfMuted) {
if (!this.currentRtcSession.isTalking) {
this.messaging.soundEffects.pushToTalk.play({ volume: 0.3 });
this._setSoundBroadcast(true);
}
this._setSoundBroadcast(true);
},
/**
* @private
@@ -1147,7 +1185,7 @@ registerModel({
if (!this.currentRtcSession.isTalking) {
return;
}
if (!this.currentRtcSession.isSelfMuted) {
if (!this.currentRtcSession.isMute) {
this.messaging.soundEffects.pushToTalk.play({ volume: 0.3 });
}
this._pushToTalkTimeoutId = browser.setTimeout(
@@ -94,7 +94,7 @@ registerModel({
* @returns {boolean}
*/
_computeIsTalking() {
return Boolean(this.rtcSession && this.rtcSession.isTalking && !this.rtcSession.isSelfMuted);
return Boolean(this.rtcSession && this.rtcSession.isTalking && !this.rtcSession.isMute);
},
/**
* @private
@@ -29,13 +29,26 @@ registerModel({
* @param {MouseEvent} ev
*/
async onClickDeafen(ev) {
await this.messaging.rtc.currentRtcSession.toggleDeaf();
if (this.messaging.rtc.currentRtcSession.isDeaf) {
this.messaging.rtc.undeafen();
} else {
this.messaging.rtc.deafen();
}
},
/**
* @param {MouseEvent} ev
*/
onClickMicrophone(ev) {
this.messaging.rtc.toggleMicrophone();
if (this.messaging.rtc.currentRtcSession.isMute) {
if (this.messaging.rtc.currentRtcSession.isSelfMuted) {
this.messaging.rtc.unmute();
}
if (this.messaging.rtc.currentRtcSession.isDeaf) {
this.messaging.rtc.undeafen();
}
} else {
this.messaging.rtc.mute();
}
},
/**
* @param {MouseEvent} ev
@@ -131,23 +131,10 @@ registerModel({
if (!this.rtc) {
return;
}
this.updateAndBroadcast({
isDeaf: !this.isDeaf,
});
for (const session of this.messaging.models['RtcSession'].all()) {
if (!session.audioElement) {
continue;
}
session.audioElement.muted = this.isDeaf;
}
if (this.channel.rtc) {
/**
* Ensures that the state of the microphone matches the deaf state
* and notifies peers.
*/
await this.async(() => this.channel.rtc.toggleMicrophone({
requestAudioDevice: false,
}));
if (this.rtc.currentRtcSession.isDeaf) {
await this.rtc.undeafen();
} else {
await this.rtc.deafen();
}
},
/**
@@ -198,6 +185,13 @@ registerModel({
return `/mail/channel/${this.channel.id}/guest/${this.guest.id}/avatar_128?unique=${this.guest.name}`;
}
},
/**
* @private
* @returns {boolean}
*/
_computeIsMute() {
return this.isSelfMuted || this.isDeaf;
},
/**
* @private
* @returns {boolean}
@@ -354,6 +348,13 @@ registerModel({
isSelfMuted: attr({
default: false,
}),
/**
* Determine whether current session is unable to speak.
*/
isMute: attr({
compute: '_computeIsMute',
default: false,
}),
/**
* Determines if the session is a session of the current partner.
* This can be true for many sessions, as one user can have multiple