Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Use native js-sdk group call support #9625

Merged
merged 4 commits into from
Nov 28, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions src/components/views/beacon/RoomCallBanner.tsx
Original file line number Diff line number Diff line change
@@ -15,34 +15,34 @@ limitations under the License.
*/

import React, { useCallback } from "react";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";

import { _t } from "../../../languageHandler";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import dispatcher, { defaultDispatcher } from "../../../dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
import { Call, ConnectionState, ElementCall } from "../../../models/Call";
import { ConnectionState, ElementCall } from "../../../models/Call";
import { useCall } from "../../../hooks/useCall";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import {
OwnBeaconStore,
OwnBeaconStoreEvent,
} from "../../../stores/OwnBeaconStore";
import { CallDurationFromEvent } from "../voip/CallDuration";
import { GroupCallDuration } from "../voip/CallDuration";
import { SdkContextClass } from "../../../contexts/SDKContext";

interface RoomCallBannerProps {
roomId: Room["roomId"];
call: Call;
call: ElementCall;
}

const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({
roomId,
call,
}) => {
const callEvent: MatrixEvent | null = (call as ElementCall)?.groupCall;

const connect = useCallback(
(ev: ButtonEvent) => {
ev.preventDefault();
@@ -57,15 +57,23 @@ const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({
);

const onClick = useCallback(() => {
const event = call.groupCall.room.currentState.getStateEvents(
EventType.GroupCallPrefix, call.groupCall.groupCallId,
);
if (event === null) {
logger.error("Couldn't find a group call event to jump to");
return;
}

dispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: roomId,
metricsTrigger: undefined,
event_id: callEvent.getId(),
event_id: event.getId(),
scroll_into_view: true,
highlighted: true,
});
}, [callEvent, roomId]);
}, [call, roomId]);

return (
<div
@@ -74,7 +82,7 @@ const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({
>
<div className="mx_RoomCallBanner_text">
<span className="mx_RoomCallBanner_label">{ _t("Video call") }</span>
<CallDurationFromEvent mxEvent={callEvent} />
<GroupCallDuration groupCall={call.groupCall} />
</div>

<AccessibleButton
@@ -119,12 +127,11 @@ const RoomCallBanner: React.FC<Props> = ({ roomId }) => {
}

// Split into outer/inner to avoid watching various parts if there is no call
if (call) {
// No banner if the call is connected (or connecting/disconnecting)
if (call.connectionState !== ConnectionState.Disconnected) return null;

return <RoomCallBannerInner call={call} roomId={roomId} />;
// No banner if the call is connected (or connecting/disconnecting)
if (call !== null && call.connectionState === ConnectionState.Disconnected) {
return <RoomCallBannerInner call={call as ElementCall} roomId={roomId} />;
}

return null;
};

52 changes: 25 additions & 27 deletions src/components/views/messages/CallEvent.tsx
Original file line number Diff line number Diff line change
@@ -18,14 +18,13 @@ import React, { forwardRef, useCallback, useContext, useMemo } from "react";

import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
import type { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Call, ConnectionState } from "../../../models/Call";
import { ConnectionState, ElementCall } from "../../../models/Call";
import { _t } from "../../../languageHandler";
import {
useCall,
useConnectionState,
useJoinCallButtonDisabled,
useJoinCallButtonTooltip,
useParticipants,
useJoinCallButtonDisabledTooltip,
useParticipatingMembers,
} from "../../../hooks/useCall";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
@@ -35,38 +34,38 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { LiveContentSummary, LiveContentType } from "../rooms/LiveContentSummary";
import FacePile from "../elements/FacePile";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { CallDuration, CallDurationFromEvent } from "../voip/CallDuration";
import { CallDuration, GroupCallDuration } from "../voip/CallDuration";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";

const MAX_FACES = 8;

interface ActiveCallEventProps {
mxEvent: MatrixEvent;
participants: Set<RoomMember>;
call: ElementCall | null;
participatingMembers: RoomMember[];
buttonText: string;
buttonKind: string;
buttonTooltip?: string;
buttonDisabled?: boolean;
buttonDisabledTooltip?: string;
onButtonClick: ((ev: ButtonEvent) => void) | null;
}

const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
(
{
mxEvent,
participants,
call,
participatingMembers,
buttonText,
buttonKind,
buttonDisabled,
buttonTooltip,
buttonDisabledTooltip,
onButtonClick,
},
ref,
) => {
const senderName = useMemo(() => mxEvent.sender?.name ?? mxEvent.getSender(), [mxEvent]);

const facePileMembers = useMemo(() => [...participants].slice(0, MAX_FACES), [participants]);
const facePileOverflow = participants.size > facePileMembers.length;
const facePileMembers = useMemo(() => participatingMembers.slice(0, MAX_FACES), [participatingMembers]);
const facePileOverflow = participatingMembers.length > facePileMembers.length;

return <div className="mx_CallEvent_wrapper" ref={ref}>
<div className="mx_CallEvent mx_CallEvent_active">
@@ -85,17 +84,17 @@ const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
type={LiveContentType.Video}
text={_t("Video call")}
active={false}
participantCount={participants.size}
participantCount={participatingMembers.length}
/>
<FacePile members={facePileMembers} faceSize={24} overflow={facePileOverflow} />
</div>
<CallDurationFromEvent mxEvent={mxEvent} />
{ call && <GroupCallDuration groupCall={call.groupCall} /> }
<AccessibleTooltipButton
className="mx_CallEvent_button"
kind={buttonKind}
disabled={onButtonClick === null || buttonDisabled}
disabled={onButtonClick === null || buttonDisabledTooltip !== undefined}
onClick={onButtonClick}
tooltip={buttonTooltip}
tooltip={buttonDisabledTooltip}
>
{ buttonText }
</AccessibleTooltipButton>
@@ -106,14 +105,13 @@ const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(

interface ActiveLoadedCallEventProps {
mxEvent: MatrixEvent;
call: Call;
call: ElementCall;
}

const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxEvent, call }, ref) => {
const connectionState = useConnectionState(call);
const participants = useParticipants(call);
const joinCallButtonTooltip = useJoinCallButtonTooltip(call);
const joinCallButtonDisabled = useJoinCallButtonDisabled(call);
const participatingMembers = useParticipatingMembers(call);
const joinCallButtonDisabledTooltip = useJoinCallButtonDisabledTooltip(call);

const connect = useCallback((ev: ButtonEvent) => {
ev.preventDefault();
@@ -142,11 +140,11 @@ const ActiveLoadedCallEvent = forwardRef<any, ActiveLoadedCallEventProps>(({ mxE
return <ActiveCallEvent
ref={ref}
mxEvent={mxEvent}
participants={participants}
call={call}
participatingMembers={participatingMembers}
buttonText={buttonText}
buttonKind={buttonKind}
buttonDisabled={joinCallButtonDisabled}
buttonTooltip={joinCallButtonTooltip}
buttonDisabledTooltip={joinCallButtonDisabledTooltip ?? undefined}
onButtonClick={onButtonClick}
/>;
});
@@ -159,7 +157,6 @@ interface CallEventProps {
* An event tile representing an active or historical Element call.
*/
export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
const noParticipants = useMemo(() => new Set<RoomMember>(), []);
const client = useContext(MatrixClientContext);
const call = useCall(mxEvent.getRoomId()!);
const latestEvent = client.getRoom(mxEvent.getRoomId())!.currentState
@@ -180,12 +177,13 @@ export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
return <ActiveCallEvent
ref={ref}
mxEvent={mxEvent}
participants={noParticipants}
call={null}
participatingMembers={[]}
buttonText={_t("Join")}
buttonKind="primary"
onButtonClick={null}
/>;
}

return <ActiveLoadedCallEvent mxEvent={mxEvent} call={call} ref={ref} />;
return <ActiveLoadedCallEvent mxEvent={mxEvent} call={call as ElementCall} ref={ref} />;
});
11 changes: 4 additions & 7 deletions src/components/views/rooms/LiveContentSummary.tsx
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ import classNames from "classnames";

import { _t } from "../../../languageHandler";
import { Call } from "../../../models/Call";
import { useParticipants } from "../../../hooks/useCall";
import { useParticipantCount } from "../../../hooks/useCall";

export enum LiveContentType {
Video,
@@ -62,13 +62,10 @@ interface LiveContentSummaryWithCallProps {
call: Call;
}

export function LiveContentSummaryWithCall({ call }: LiveContentSummaryWithCallProps) {
const participants = useParticipants(call);

return <LiveContentSummary
export const LiveContentSummaryWithCall: FC<LiveContentSummaryWithCallProps> = ({ call }) =>
<LiveContentSummary
type={LiveContentType.Video}
text={_t("Video")}
active={false}
participantCount={participants.size}
participantCount={useParticipantCount(call)}
/>;
}
6 changes: 3 additions & 3 deletions src/components/views/rooms/RoomHeader.tsx
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ import IconizedContextMenu, {
IconizedContextMenuRadio,
} from "../context_menus/IconizedContextMenu";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { CallDurationFromEvent } from "../voip/CallDuration";
import { GroupCallDuration } from "../voip/CallDuration";
import { Alignment } from "../elements/Tooltip";
import RoomCallBanner from '../beacon/RoomCallBanner';

@@ -512,7 +512,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
}

if (this.props.viewingCall && this.props.activeCall instanceof ElementCall) {
startButtons.push(<CallLayoutSelector call={this.props.activeCall} />);
startButtons.push(<CallLayoutSelector key="layout" call={this.props.activeCall} />);
}

if (!this.props.viewingCall && this.props.onForgetClick) {
@@ -685,7 +685,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
{ _t("Video call") }
</div>
{ this.props.activeCall instanceof ElementCall && (
<CallDurationFromEvent mxEvent={this.props.activeCall.groupCall} />
<GroupCallDuration groupCall={this.props.activeCall.groupCall} />
) }
{ /* Empty topic element to fill out space */ }
<div className="mx_RoomHeader_topic" />
9 changes: 3 additions & 6 deletions src/components/views/rooms/RoomTileCallSummary.tsx
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ import React, { FC } from "react";

import type { Call } from "../../../models/Call";
import { _t } from "../../../languageHandler";
import { useConnectionState, useParticipants } from "../../../hooks/useCall";
import { useConnectionState, useParticipantCount } from "../../../hooks/useCall";
import { ConnectionState } from "../../../models/Call";
import { LiveContentSummary, LiveContentType } from "./LiveContentSummary";

@@ -27,13 +27,10 @@ interface Props {
}

export const RoomTileCallSummary: FC<Props> = ({ call }) => {
const connectionState = useConnectionState(call);
const participants = useParticipants(call);

let text: string;
let active: boolean;

switch (connectionState) {
switch (useConnectionState(call)) {
case ConnectionState.Disconnected:
text = _t("Video");
active = false;
@@ -53,6 +50,6 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
type={LiveContentType.Video}
text={text}
active={active}
participantCount={participants.size}
participantCount={useParticipantCount(call)}
/>;
};
22 changes: 12 additions & 10 deletions src/components/views/voip/CallDuration.tsx
Original file line number Diff line number Diff line change
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { FC, useState, useEffect } from "react";
import React, { FC, useState, useEffect, memo } from "react";
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";

import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { formatCallTime } from "../../../DateUtils";

interface CallDurationProps {
@@ -26,26 +26,28 @@ interface CallDurationProps {
/**
* A call duration counter.
*/
export const CallDuration: FC<CallDurationProps> = ({ delta }) => {
export const CallDuration: FC<CallDurationProps> = memo(({ delta }) => {
// Clock desync could lead to a negative duration, so just hide it if that happens
if (delta <= 0) return null;
return <div className="mx_CallDuration">{ formatCallTime(new Date(delta)) }</div>;
};
});

interface CallDurationFromEventProps {
mxEvent: MatrixEvent;
interface GroupCallDurationProps {
groupCall: GroupCall;
}

/**
* A call duration counter that automatically counts up, given the event that
* started the call.
* A call duration counter that automatically counts up, given a live GroupCall
* object.
*/
export const CallDurationFromEvent: FC<CallDurationFromEventProps> = ({ mxEvent }) => {
export const GroupCallDuration: FC<GroupCallDurationProps> = ({ groupCall }) => {
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
const timer = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(timer);
}, []);

return <CallDuration delta={now - mxEvent.getTs()} />;
return groupCall.creationTs === null
? null
: <CallDuration delta={now - groupCall.creationTs} />;
};
Loading