From 3fdf4d13ca3af53966ae2c4083ce5dc1bd4dd3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 17 Aug 2021 09:48:25 +0200 Subject: [PATCH 1/3] Proper init in constructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/LoggedInView.tsx | 2 +- src/components/views/voip/AudioFeedArrayForCall.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 2392a8b28d..85fca1ae77 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -156,7 +156,7 @@ class LoggedInView extends React.Component { // use compact timeline view useCompactLayout: SettingsStore.getValue('useCompactLayout'), usageLimitDismissed: false, - activeCalls: [], + activeCalls: CallHandler.sharedInstance().getAllActiveCalls(), }; // stash the MatrixClient in case we log out before we are unmounted diff --git a/src/components/views/voip/AudioFeedArrayForCall.tsx b/src/components/views/voip/AudioFeedArrayForCall.tsx index 958ac2a8d4..a7dd0283ff 100644 --- a/src/components/views/voip/AudioFeedArrayForCall.tsx +++ b/src/components/views/voip/AudioFeedArrayForCall.tsx @@ -32,7 +32,7 @@ export default class AudioFeedArrayForCall extends React.Component Date: Tue, 17 Aug 2021 09:51:48 +0200 Subject: [PATCH 2/3] Properly listen for call_state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous thing should have never worked, since CallsChanged doesn't get emitted when a call is answered Signed-off-by: Šimon Brandner --- src/components/structures/LoggedInView.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 85fca1ae77..4a8b77abec 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -55,7 +55,7 @@ import { getKeyBindingsManager, NavigationAction, RoomAction } from '../../KeyBi import { IOpts } from "../../createRoom"; import SpacePanel from "../views/spaces/SpacePanel"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import CallHandler, { CallHandlerEvent } from '../../CallHandler'; +import CallHandler from '../../CallHandler'; import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall'; import RoomView from './RoomView'; @@ -142,6 +142,7 @@ interface IState { class LoggedInView extends React.Component { static displayName = 'LoggedInView'; + private dispatcherRef: string; protected readonly _matrixClient: MatrixClient; protected readonly _roomView: React.RefObject; protected readonly _resizeContainer: React.RefObject; @@ -172,7 +173,7 @@ class LoggedInView extends React.Component { componentDidMount() { document.addEventListener('keydown', this.onNativeKeyDown, false); - CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged); + this.dispatcherRef = dis.register(this.onAction); this.updateServerNoticeEvents(); @@ -197,7 +198,7 @@ class LoggedInView extends React.Component { componentWillUnmount() { document.removeEventListener('keydown', this.onNativeKeyDown, false); - CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged); + dis.unregister(this.dispatcherRef); this._matrixClient.removeListener("accountData", this.onAccountData); this._matrixClient.removeListener("sync", this.onSync); this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents); @@ -205,10 +206,16 @@ class LoggedInView extends React.Component { this.resizer.detach(); } - private onCallsChanged = () => { - this.setState({ - activeCalls: CallHandler.sharedInstance().getAllActiveCalls(), - }); + private onAction = (payload): void => { + switch (payload.action) { + case 'call_state': { + const activeCalls = CallHandler.sharedInstance().getAllActiveCalls(); + if (activeCalls !== this.state.activeCalls) { + this.setState({ activeCalls }); + } + break; + } + } }; public canResetTimelineInRoom = (roomId: string) => { From 1d664f091431fd4303ea10b55857ebd8fe51f25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 17 Aug 2021 10:23:54 +0200 Subject: [PATCH 3/3] Explicitly handle first state change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/CallHandler.tsx | 165 +++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 79 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index f2142f56f4..e11e828864 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -464,85 +464,7 @@ export default class CallHandler extends EventEmitter { this.removeCallForRoom(mappedRoomId); }); call.on(CallEvent.State, (newState: CallState, oldState: CallState) => { - if (!this.matchesCallForThisRoom(call)) return; - - this.setCallState(call, newState); - - switch (oldState) { - case CallState.Ringing: - this.pause(AudioID.Ring); - break; - case CallState.InviteSent: - this.pause(AudioID.Ringback); - break; - } - - if (newState !== CallState.Ringing) { - this.silencedCalls.delete(call.callId); - } - - switch (newState) { - case CallState.Ringing: { - const incomingCallPushRule = ( - new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) - ); - const pushRuleEnabled = incomingCallPushRule?.enabled; - const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( - action.set_tweak === TweakName.Sound && - action.value === "ring" - )); - - if (pushRuleEnabled && tweakSetToRing) { - this.play(AudioID.Ring); - } else { - this.silenceCall(call.callId); - } - break; - } - case CallState.InviteSent: { - this.play(AudioID.Ringback); - break; - } - case CallState.Ended: { - const hangupReason = call.hangupReason; - Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason); - this.removeCallForRoom(mappedRoomId); - if (oldState === CallState.InviteSent && call.hangupParty === CallParty.Remote) { - this.play(AudioID.Busy); - - // Don't show a modal when we got rejected/the call was hung up - if (!hangupReason || [CallErrorCode.UserHangup, "user hangup"].includes(hangupReason)) break; - - let title; - let description; - // TODO: We should either do away with these or figure out a copy for each code (expect user_hangup...) - if (call.hangupReason === CallErrorCode.UserBusy) { - title = _t("User Busy"); - description = _t("The user you called is busy."); - } else { - title = _t("Call Failed"); - description = _t("The call could not be established"); - } - - Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { - title, description, - }); - } else if ( - hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting - ) { - Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { - title: _t("Answered Elsewhere"), - description: _t("The call was answered on another device."), - }); - } else if (oldState !== CallState.Fledgling && oldState !== CallState.Ringing) { - // don't play the end-call sound for calls that never got off the ground - this.play(AudioID.CallEnd); - } - - this.logCallStats(call, mappedRoomId); - break; - } - } + this.onCallStateChanged(newState, oldState, call); }); call.on(CallEvent.Replaced, (newCall: MatrixCall) => { if (!this.matchesCallForThisRoom(call)) return; @@ -598,6 +520,89 @@ export default class CallHandler extends EventEmitter { }); } + private onCallStateChanged = (newState: CallState, oldState: CallState, call: MatrixCall): void => { + if (!this.matchesCallForThisRoom(call)) return; + + const mappedRoomId = this.roomIdForCall(call); + this.setCallState(call, newState); + + switch (oldState) { + case CallState.Ringing: + this.pause(AudioID.Ring); + break; + case CallState.InviteSent: + this.pause(AudioID.Ringback); + break; + } + + if (newState !== CallState.Ringing) { + this.silencedCalls.delete(call.callId); + } + + switch (newState) { + case CallState.Ringing: { + const incomingCallPushRule = ( + new PushProcessor(MatrixClientPeg.get()).getPushRuleById(RuleId.IncomingCall) + ); + const pushRuleEnabled = incomingCallPushRule?.enabled; + const tweakSetToRing = incomingCallPushRule?.actions.some((action: Tweaks) => ( + action.set_tweak === TweakName.Sound && + action.value === "ring" + )); + + if (pushRuleEnabled && tweakSetToRing) { + this.play(AudioID.Ring); + } else { + this.silenceCall(call.callId); + } + break; + } + case CallState.InviteSent: { + this.play(AudioID.Ringback); + break; + } + case CallState.Ended: { + const hangupReason = call.hangupReason; + Analytics.trackEvent('voip', 'callEnded', 'hangupReason', hangupReason); + this.removeCallForRoom(mappedRoomId); + if (oldState === CallState.InviteSent && call.hangupParty === CallParty.Remote) { + this.play(AudioID.Busy); + + // Don't show a modal when we got rejected/the call was hung up + if (!hangupReason || [CallErrorCode.UserHangup, "user hangup"].includes(hangupReason)) break; + + let title; + let description; + // TODO: We should either do away with these or figure out a copy for each code (expect user_hangup...) + if (call.hangupReason === CallErrorCode.UserBusy) { + title = _t("User Busy"); + description = _t("The user you called is busy."); + } else { + title = _t("Call Failed"); + description = _t("The call could not be established"); + } + + Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { + title, description, + }); + } else if ( + hangupReason === CallErrorCode.AnsweredElsewhere && oldState === CallState.Connecting + ) { + Modal.createTrackedDialog('Call Handler', 'Call Failed', ErrorDialog, { + title: _t("Answered Elsewhere"), + description: _t("The call was answered on another device."), + }); + } else if (oldState !== CallState.Fledgling && oldState !== CallState.Ringing) { + // don't play the end-call sound for calls that never got off the ground + this.play(AudioID.CallEnd); + } + + this.logCallStats(call, mappedRoomId); + break; + } + } + }; + private async logCallStats(call: MatrixCall, mappedRoomId: string) { const stats = await call.getCurrentCallStats(); logger.debug( @@ -861,6 +866,8 @@ export default class CallHandler extends EventEmitter { this.calls.set(mappedRoomId, call); this.emit(CallHandlerEvent.CallsChanged, this.calls); this.setCallListeners(call); + // Explicitly handle first state change + this.onCallStateChanged(call.state, null, call); // get ready to send encrypted events in the room, so if the user does answer // the call, we'll be ready to send. NB. This is the protocol-level room ID not