New group call experience: Starting and ending calls (#9318)

* Create m.room calls in video rooms, and m.prompt calls otherwise

* Terminate a call when the last person leaves

* Hook up the room header button to a unified CallView component

* Write more tests
This commit is contained in:
Robin 2022-09-27 07:54:51 -04:00 committed by GitHub
parent 54b79c7667
commit ace6591f43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 695 additions and 512 deletions

View file

@ -77,7 +77,7 @@ import EffectsOverlay from "../views/elements/EffectsOverlay";
import { containsEmoji } from '../../effects/utils';
import { CHAT_EFFECTS } from '../../effects';
import WidgetStore from "../../stores/WidgetStore";
import { VideoRoomView } from "./VideoRoomView";
import { CallView } from "../views/voip/CallView";
import { UPDATE_EVENT } from "../../stores/AsyncStore";
import Notifier from "../../Notifier";
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
@ -120,6 +120,7 @@ import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
import { LargeLoader } from './LargeLoader';
import { VoiceBroadcastInfoEventType } from '../../voice-broadcast';
import { isVideoRoom } from '../../utils/video-rooms';
import { CallStore, CallStoreEvent } from "../../stores/CallStore";
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -442,6 +443,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate);
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
CallStore.instance.on(CallStoreEvent.ActiveCalls, this.onActiveCalls);
this.props.resizeNotifier.on("isResizing", this.onIsResizing);
this.settingWatchers = [
@ -514,7 +517,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
};
private getMainSplitContentType = (room: Room) => {
if (SettingsStore.getValue("feature_video_rooms") && isVideoRoom(room)) {
if (
(SettingsStore.getValue("feature_group_calls") && RoomViewStore.instance.isViewingCall())
|| isVideoRoom(room)
) {
return MainSplitContentType.Call;
}
if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) {
@ -544,6 +550,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
const roomId = RoomViewStore.instance.getRoomId();
const room = this.context.getRoom(roomId);
// This convoluted type signature ensures we get IntelliSense *and* correct typing
const newState: Partial<IRoomState> & Pick<IRoomState, any> = {
@ -561,13 +568,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
wasContextSwitch: RoomViewStore.instance.getWasContextSwitch(),
mainSplitContentType: room === null ? undefined : this.getMainSplitContentType(room),
initialEventId: null, // default to clearing this, will get set later in the method if needed
showRightPanel: RightPanelStore.instance.isOpenForRoom(roomId),
};
const initialEventId = RoomViewStore.instance.getInitialEventId();
if (initialEventId) {
const room = this.context.getRoom(roomId);
let initialEvent = room?.findEventById(initialEventId);
// The event does not exist in the current sync data
// We need to fetch it to know whether to route this request
@ -693,6 +700,18 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}
};
private onActiveCalls = () => {
if (this.state.roomId !== undefined && !CallStore.instance.hasActiveCall(this.state.roomId)) {
// We disconnected from the call, so stop viewing it
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: this.state.roomId,
view_call: false,
metricsTrigger: undefined,
}, true); // Synchronous so that CallView disappears immediately
}
};
private getRoomId = () => {
// According to `onRoomViewStoreUpdate`, `state.roomId` can be null
// if we have a room alias we haven't resolved yet. To work around this,
@ -894,6 +913,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
);
}
CallStore.instance.off(CallStoreEvent.ActiveCalls, this.onActiveCalls);
LegacyCallHandler.instance.off(LegacyCallHandlerEvent.CallState, this.onCallState);
// cancel any pending calls to the throttled updated
@ -2324,7 +2344,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const mainClasses = classNames("mx_RoomView", {
mx_RoomView_inCall: Boolean(activeCall),
mx_RoomView_immersive: this.state.mainSplitContentType === MainSplitContentType.Call,
mx_RoomView_immersive: this.state.mainSplitContentType !== MainSplitContentType.Timeline,
});
const showChatEffects = SettingsStore.getValue('showChatEffects');
@ -2366,9 +2386,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
</>;
break;
case MainSplitContentType.Call: {
mainSplitContentClassName = "mx_MainSplit_video";
mainSplitContentClassName = "mx_MainSplit_call";
mainSplitBody = <>
<VideoRoomView room={this.state.room} resizing={this.state.resizing} />
<CallView
room={this.state.room}
resizing={this.state.resizing}
waitForCall={isVideoRoom(this.state.room)}
/>
{ previewBar }
</>;
}