diff --git a/src/components/structures/CallEventGrouper.ts b/src/components/structures/CallEventGrouper.ts new file mode 100644 index 0000000000..41e67f580d --- /dev/null +++ b/src/components/structures/CallEventGrouper.ts @@ -0,0 +1,90 @@ +/* +Copyright 2021 Šimon Brandner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call"; +import CallHandler from '../../CallHandler'; +import { EventEmitter } from 'events'; + +export enum CallEventGrouperState { + Incoming = "incoming", + Ended = "ended", +} + +export enum CallEventGrouperEvent { + StateChanged = "state_changed", +} + +export default class CallEventGrouper extends EventEmitter { + invite: MatrixEvent; + call: MatrixCall; + state: CallEventGrouperState; + + public answerCall() { + this.call?.answer(); + } + + public rejectCall() { + this.call?.reject(); + } + + public callBack() { + + } + + public isVoice(): boolean { + const invite = this.invite; + + // FIXME: Find a better way to determine this from the event? + let isVoice = true; + if ( + invite.getContent().offer && invite.getContent().offer.sdp && + invite.getContent().offer.sdp.indexOf('m=video') !== -1 + ) { + isVoice = false; + } + + return isVoice; + } + + public getState() { + return this.state; + } + + private setCallListeners() { + this.call.addListener(CallEvent.State, this.setCallState); + } + + private setCallState = () => { + if (this.call?.state === CallState.Ringing) { + this.state = CallEventGrouperState.Incoming; + } + this.emit(CallEventGrouperEvent.StateChanged, this.state); + } + + public add(event: MatrixEvent) { + if (event.getType() === EventType.CallInvite) this.invite = event; + + if (this.call) return; + const callId = event.getContent().call_id; + this.call = CallHandler.sharedInstance().getCallById(callId); + if (!this.call) return; + this.setCallListeners(); + this.setCallState(); + } +} diff --git a/src/components/structures/CallEventGrouper.tsx b/src/components/structures/CallEventGrouper.tsx deleted file mode 100644 index 3b6d18310c..0000000000 --- a/src/components/structures/CallEventGrouper.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2021 Šimon Brandner - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - - -import { EventType } from "matrix-js-sdk/src/@types/event"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; - -export interface TimelineCallState { - callId: string; - isVoice: boolean; -} - -export default class CallEventGrouper { - invite: MatrixEvent; - callId: string; - - private isVoice(): boolean { - const invite = this.invite; - - // FIXME: Find a better way to determine this from the event? - let isVoice = true; - if ( - invite.getContent().offer && invite.getContent().offer.sdp && - invite.getContent().offer.sdp.indexOf('m=video') !== -1 - ) { - isVoice = false; - } - - return isVoice; - } - - public add(event: MatrixEvent) { - if (event.getType() === EventType.CallInvite) this.invite = event; - this.callId = event.getContent().call_id; - } - - public getState(): TimelineCallState { - return { - isVoice: this.isVoice(), - callId: this.callId, - } - } -} diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index ab5fe01e47..b6d9f619c8 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -662,7 +662,7 @@ export default class MessagePanel extends React.Component { // it's successful: we received it. isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId(); - const callState = this._callEventGroupers.get(mxEv.getContent().call_id)?.getState(); + const callEventGrouper = this._callEventGroupers.get(mxEv.getContent().call_id); // use txnId as key if available so that we don't remount during sending ret.push( @@ -696,7 +696,7 @@ export default class MessagePanel extends React.Component { layout={this.props.layout} enableFlair={this.props.enableFlair} showReadReceipts={this.props.showReadReceipts} - callState={callState} + callEventGrouper={callEventGrouper} /> , diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index 68e153546f..88b1498272 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -19,15 +19,39 @@ import React from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from '../../../languageHandler'; import MemberAvatar from '../avatars/MemberAvatar'; -import { TimelineCallState } from '../../structures/CallEventGrouper'; +import CallEventGrouper, { CallEventGrouperEvent, CallEventGrouperState } from '../../structures/CallEventGrouper'; +import FormButton from '../elements/FormButton'; interface IProps { mxEvent: MatrixEvent; - timelineCallState: TimelineCallState; -} + callEventGrouper: CallEventGrouper; } -export default class CallEvent extends React.Component { +interface IState { + callState: CallEventGrouperState; +} + +export default class CallEvent extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + callState: this.props.callEventGrouper.getState(), + } + } + + componentDidMount() { + this.props.callEventGrouper.addListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); + } + + componentWillUnmount() { + this.props.callEventGrouper.removeListener(CallEventGrouperEvent.StateChanged, this.onStateChanged); + } + + private onStateChanged = (newState: CallEventGrouperState) => { + this.setState({callState: newState}); + } + render() { const event = this.props.mxEvent; const sender = event.sender ? event.sender.name : event.getSender(); @@ -47,7 +71,7 @@ export default class CallEvent extends React.Component { { sender }
- { this.props.timelineCallState.isVoice ? _t("Voice call") : _t("Video call") } + { this.props.callEventGrouper.isVoice() ? _t("Voice call") : _t("Video call") }
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index eb76354975..930be62fbf 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -46,7 +46,7 @@ import { EditorStateTransfer } from "../../../utils/EditorStateTransfer"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import NotificationBadge from "./NotificationBadge"; -import { TimelineCallState } from "../../structures/CallEventGrouper"; +import CallEventGrouper from "../../structures/CallEventGrouper"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -277,7 +277,7 @@ interface IProps { permalinkCreator?: RoomPermalinkCreator; // CallEventGrouper for this event - callState?: TimelineCallState; + callEventGrouper?: CallEventGrouper; } interface IState { @@ -1143,7 +1143,7 @@ export default class EventTile extends React.Component { showUrlPreview={this.props.showUrlPreview} permalinkCreator={this.props.permalinkCreator} onHeightChanged={this.props.onHeightChanged} - callState={this.props.callState} + callEventGrouper={this.props.callEventGrouper} /> { keyRequestInfo } { reactionsRow }