Merge remote-tracking branch 'upstream/main' into feature_sfu

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner
2023-01-25 17:13:01 +01:00
16 changed files with 315 additions and 148 deletions

View File

@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Sentry from "@sentry/react";
import { Resizable } from "re-resizable";
import React, {
useEffect,
@@ -34,6 +35,7 @@ import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
import styles from "./GroupCallInspector.module.css";
import { SelectInput } from "../input/SelectInput";
import { PosthogAnalytics } from "../PosthogAnalytics";
interface InspectorContextState {
eventsByUserId?: { [userId: string]: SequenceDiagramMatrixEvent[] };
@@ -108,6 +110,19 @@ function formatTimestamp(timestamp: number | Date) {
return dateFormatter.format(timestamp);
}
function formatType(event: SequenceDiagramMatrixEvent): string {
if (event.content.msgtype === "m.bad.encrypted") return "Undecryptable";
return event.type;
}
function lineForEvent(event: SequenceDiagramMatrixEvent): string {
return `${getUserName(event.from)} ${
event.ignored ? "-x" : "->>"
} ${getUserName(event.to)}: ${formatTimestamp(event.timestamp)} ${formatType(
event
)} ${formatContent(event.type, event.content)}`;
}
export const InspectorContext =
createContext<
[
@@ -187,21 +202,7 @@ export function SequenceDiagramViewer({
participant ${getUserName(localUserId)}
participant Room
participant ${selectedUserId ? getUserName(selectedUserId) : "unknown"}
${
events
? events
.map(
({ to, from, timestamp, type, content, ignored }) =>
`${getUserName(from)} ${ignored ? "-x" : "->>"} ${getUserName(
to
)}: ${formatTimestamp(timestamp)} ${type} ${formatContent(
type,
content
)}`
)
.join("\n ")
: ""
}
${events ? events.map(lineForEvent).join("\n ") : ""}
`;
mermaid.mermaidAPI.render("mermaid", graphDefinition, (svgCode: string) => {
@@ -389,12 +390,23 @@ function useGroupCallState(
function onSendVoipEvent(event: Record<string, unknown>) {
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
}
function onUndecryptableToDevice(event: MatrixEvent) {
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
Sentry.captureMessage("Undecryptable to-device Event");
PosthogAnalytics.instance.eventUndecryptableToDevice.track(
groupCall.groupCallId
);
}
client.on(RoomStateEvent.Events, onUpdateRoomState);
//groupCall.on("calls_changed", onCallsChanged);
groupCall.on(CallEvent.SendVoipEvent, onSendVoipEvent);
//client.on("state", onCallsChanged);
//client.on("hangup", onCallHangup);
client.on(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
client.on(ClientEvent.UndecryptableToDeviceEvent, onUndecryptableToDevice);
onUpdateRoomState();
@@ -405,6 +417,10 @@ function useGroupCallState(
//client.removeListener("state", onCallsChanged);
//client.removeListener("hangup", onCallHangup);
client.removeListener(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
client.removeListener(
ClientEvent.UndecryptableToDeviceEvent,
onUndecryptableToDevice
);
};
}, [client, groupCall]);

View File

@@ -17,6 +17,7 @@ limitations under the License.
import React, { useCallback, useState, useRef } from "react";
import classNames from "classnames";
import { useSpring, animated } from "@react-spring/web";
import { logger } from "@sentry/utils";
import styles from "./PTTButton.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
@@ -68,11 +69,23 @@ export const PTTButton: React.FC<Props> = ({
enqueueNetworkWaiting(true, 100);
startTalking();
}, [enqueueNetworkWaiting, startTalking, buttonHeld]);
const unhold = useCallback(() => {
if (!buttonHeld) return;
setButtonHeld(false);
setNetworkWaiting(false);
stopTalking();
}, [setNetworkWaiting, stopTalking]);
}, [setNetworkWaiting, stopTalking, buttonHeld]);
const onMouseUp = useCallback(() => {
logger.info("Mouse up event: unholding PTT button");
unhold();
}, [unhold]);
const onBlur = useCallback(() => {
logger.info("Blur event: unholding PTT button");
unhold();
}, [unhold]);
const onButtonMouseDown = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
@@ -85,7 +98,7 @@ export const PTTButton: React.FC<Props> = ({
// These listeners go on the window so even if the user's cursor / finger
// leaves the button while holding it, the button stays pushed until
// they stop clicking / tapping.
useEventTarget(window, "mouseup", unhold);
useEventTarget(window, "mouseup", onMouseUp);
useEventTarget(
window,
"touchend",
@@ -103,6 +116,8 @@ export const PTTButton: React.FC<Props> = ({
}
if (!touchFound) return;
logger.info("Touch event ended: unholding PTT button");
e.preventDefault();
unhold();
setActiveTouchId(null);
@@ -163,6 +178,8 @@ export const PTTButton: React.FC<Props> = ({
e.preventDefault();
logger.info("Keyup event for spacebar: unholding PTT button");
unhold();
}
},
@@ -171,7 +188,7 @@ export const PTTButton: React.FC<Props> = ({
);
// TODO: We will need to disable this for a global PTT hotkey to work
useEventTarget(window, "blur", unhold);
useEventTarget(window, "blur", onBlur);
const prefersReducedMotion = usePrefersReducedMotion();
const { shadow } = useSpring({

View File

@@ -210,36 +210,36 @@ export const PTTCallView: React.FC<Props> = ({
</Header>
)}
<div className={styles.center}>
{showControls && (
<>
<div className={styles.participants}>
<p>
{t("{{count}} people connected", {
count: participatingMembers.length,
})}
</p>
<Facepile
size={facepileSize}
max={8}
className={styles.facepile}
client={client}
members={participatingMembers}
/>
</div>
<div className={styles.footer}>
<OverflowMenu
inCall
roomIdOrAlias={roomIdOrAlias}
groupCall={groupCall}
showInvite={false}
feedbackModalState={feedbackModalState}
feedbackModalProps={feedbackModalProps}
/>
{!isEmbedded && <HangupButton onPress={onLeave} />}
<InviteButton onPress={() => inviteModalState.open()} />
</div>
</>
)}
{/* Always render this because the window will become shorter when the on-screen
keyboard appears, so if we don't render it, the dialog will unmount. */}
<div style={{ display: showControls ? "block" : "none" }}>
<div className={styles.participants}>
<p>
{t("{{count}} people connected", {
count: participatingMembers.length,
})}
</p>
<Facepile
size={facepileSize}
max={8}
className={styles.facepile}
client={client}
members={participatingMembers}
/>
</div>
<div className={styles.footer}>
<OverflowMenu
inCall
roomIdOrAlias={roomIdOrAlias}
groupCall={groupCall}
showInvite={false}
feedbackModalState={feedbackModalState}
feedbackModalProps={feedbackModalProps}
/>
{!isEmbedded && <HangupButton onPress={onLeave} />}
<InviteButton onPress={() => inviteModalState.open()} />
</div>
</div>
<div className={styles.pttButtonContainer}>
{showControls &&

View File

@@ -50,9 +50,9 @@ export const RoomPage: FC = () => {
const [isRegistering, setIsRegistering] = useState(false);
useEffect(() => {
// If we're not already authed and we've been given a display name as
// If we've finished loading, are not already authed and we've been given a display name as
// a URL param, automatically register a passwordless user
if (!isAuthenticated && displayName) {
if (!loading && !isAuthenticated && displayName) {
setIsRegistering(true);
registerPasswordlessUser(displayName).finally(() => {
setIsRegistering(false);
@@ -63,6 +63,7 @@ export const RoomPage: FC = () => {
displayName,
setIsRegistering,
registerPasswordlessUser,
loading,
]);
const groupCallView = useCallback(

View File

@@ -52,12 +52,22 @@ export const useLoadGroupCall = (
const fetchOrCreateRoom = async (): Promise<Room> => {
try {
const room = await client.joinRoom(roomIdOrAlias, { viaServers });
// We lowercase the localpart when we create the room, so we must lowercase
// it here too (we just do the whole alias). We can't do the same to room IDs
// though.
const sanitisedIdOrAlias =
roomIdOrAlias[0] === "#"
? roomIdOrAlias.toLowerCase()
: roomIdOrAlias;
const room = await client.joinRoom(sanitisedIdOrAlias, {
viaServers,
});
logger.info(
`Joined ${roomIdOrAlias}, waiting room to be ready for group calls`
`Joined ${sanitisedIdOrAlias}, waiting room to be ready for group calls`
);
await client.waitUntilRoomReadyForGroupCalls(room.roomId);
logger.info(`${roomIdOrAlias}, is ready for group calls`);
logger.info(`${sanitisedIdOrAlias}, is ready for group calls`);
return room;
} catch (error) {
if (

View File

@@ -113,12 +113,14 @@ export const usePTT = (
},
setState,
] = useState(() => {
// slightly concerningly, this can end up null as we seem to sometimes get
// here before the room state contains our own member event
const roomMember = groupCall.room.getMember(client.getUserId());
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
return {
isAdmin: roomMember.powerLevel >= 100,
isAdmin: roomMember ? roomMember.powerLevel >= 100 : false,
talkOverEnabled: false,
pttButtonHeld: false,
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,