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
Show file tree
Hide file tree
Changes from 3 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
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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;
};

Expand Down
52 changes: 25 additions & 27 deletions src/components/views/messages/CallEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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">
Expand All @@ -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>
Expand All @@ -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();
Expand Down Expand Up @@ -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}
/>;
});
Expand All @@ -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
Expand All @@ -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
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Up @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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" />
Expand Down
9 changes: 3 additions & 6 deletions src/components/views/rooms/RoomTileCallSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
Expand All @@ -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
Expand Up @@ -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 {
Expand All @@ -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