From 918736e758082720d0a7c7585d8f261414d160fe Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 23 Aug 2023 10:06:08 +0100 Subject: [PATCH] Removed now unused useGroupCall & OpenIDLoader --- src/livekit/OpenIDLoader.tsx | 92 ----- src/room/useGroupCall.ts | 644 ----------------------------------- 2 files changed, 736 deletions(-) delete mode 100644 src/livekit/OpenIDLoader.tsx delete mode 100644 src/room/useGroupCall.ts diff --git a/src/livekit/OpenIDLoader.tsx b/src/livekit/OpenIDLoader.tsx deleted file mode 100644 index 911a1ae7..00000000 --- a/src/livekit/OpenIDLoader.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2023 New Vector Ltd - -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 { - ReactNode, - createContext, - useContext, - useEffect, - useState, -} from "react"; -import { logger } from "matrix-js-sdk/src/logger"; -import { GroupCall } from "matrix-js-sdk"; - -import { - OpenIDClientParts, - SFUConfig, - getSFUConfigWithOpenID, -} from "./openIDSFU"; -import { ErrorView, LoadingView } from "../FullScreenView"; - -interface Props { - client: OpenIDClientParts; - groupCall: GroupCall; - roomName: string; - children: ReactNode; -} - -const SFUConfigContext = createContext(undefined); - -export const useSFUConfig = () => useContext(SFUConfigContext); - -export function OpenIDLoader({ client, groupCall, roomName, children }: Props) { - const [state, setState] = useState< - SFUConfigLoading | SFUConfigLoaded | SFUConfigFailed - >({ kind: "loading" }); - - useEffect(() => { - (async () => { - try { - const result = await getSFUConfigWithOpenID( - client, - groupCall, - roomName - ); - setState({ kind: "loaded", sfuConfig: result }); - } catch (e) { - logger.error("Failed to fetch SFU config: ", e); - setState({ kind: "failed", error: e as Error }); - } - })(); - }, [client, groupCall, roomName]); - - switch (state.kind) { - case "loading": - return ; - case "failed": - return ; - case "loaded": - return ( - - {children} - - ); - } -} - -type SFUConfigLoading = { - kind: "loading"; -}; - -type SFUConfigLoaded = { - kind: "loaded"; - sfuConfig: SFUConfig; -}; - -type SFUConfigFailed = { - kind: "failed"; - error: Error; -}; diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts deleted file mode 100644 index 4f6d3ad7..00000000 --- a/src/room/useGroupCall.ts +++ /dev/null @@ -1,644 +0,0 @@ -/* -Copyright 2022-2023 New Vector Ltd - -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 { useCallback, useEffect, useReducer, useState } from "react"; -import * as Sentry from "@sentry/react"; -import { - GroupCallEvent, - GroupCallState, - GroupCall, - GroupCallErrorCode, - GroupCallUnknownDeviceError, - GroupCallError, - GroupCallStatsReportEvent, - GroupCallStatsReport, -} from "matrix-js-sdk/src/webrtc/groupCall"; -import { CallFeed, CallFeedEvent } from "matrix-js-sdk/src/webrtc/callFeed"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { useTranslation } from "react-i18next"; -import { IWidgetApiRequest } from "matrix-widget-api"; -import { MatrixClient, RoomStateEvent } from "matrix-js-sdk"; -import { - ByteSentStatsReport, - ConnectionStatsReport, - SummaryStatsReport, - CallFeedReport, -} from "matrix-js-sdk/src/webrtc/stats/statsReport"; -import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; - -import { usePageUnload } from "./usePageUnload"; -import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; -import { TranslatedError, translatedError } from "../TranslatedError"; -import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget"; -import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; -import { ElementCallOpenTelemetry } from "../otel/otel"; -import { checkForParallelCalls } from "./checkForParallelCalls"; - -enum ConnectionState { - EstablishingCall = "establishing call", // call hasn't been established yet - WaitMedia = "wait_media", // call is set up, waiting for ICE to connect - Connected = "connected", // media is flowing -} - -export interface ParticipantInfo { - connectionState: ConnectionState; - presenter: boolean; -} - -interface UseGroupCallReturnType { - state: GroupCallState; - localCallFeed?: CallFeed; - activeSpeaker?: CallFeed; - userMediaFeeds: CallFeed[]; - microphoneMuted: boolean; - localVideoMuted: boolean; - error?: TranslatedError; - initLocalCallFeed: () => void; - enter: () => Promise; - leave: () => void; - toggleLocalVideoMuted: () => void; - toggleMicrophoneMuted: () => void; - toggleScreensharing: () => void; - setMicrophoneMuted: (muted: boolean) => void; - requestingScreenshare: boolean; - isScreensharing: boolean; - screenshareFeeds: CallFeed[]; - participants: Map>; - hasLocalParticipant: boolean; - otelGroupCallMembership?: OTelGroupCallMembership; -} - -interface State { - state: GroupCallState; - localCallFeed?: CallFeed; - activeSpeaker?: CallFeed; - userMediaFeeds: CallFeed[]; - error?: TranslatedError; - microphoneMuted: boolean; - localVideoMuted: boolean; - screenshareFeeds: CallFeed[]; - isScreensharing: boolean; - requestingScreenshare: boolean; - participants: Map>; - hasLocalParticipant: boolean; -} - -// This is a bit of a hack, but we keep the opentelemetry tracker object at the file -// level so that it doesn't pop in & out of existence as react mounts & unmounts -// components. The right solution is probably for this to live in the js-sdk and have -// the same lifetime as groupcalls themselves. -let groupCallOTelMembership: OTelGroupCallMembership | undefined; -let groupCallOTelMembershipGroupCallId: string; - -/*function getParticipants( - rtcSession: MatrixRTCSession -): Map> { - const participants = new Map>(); - - for (const membership of rtcSession.memberships) { - const participantInfoMap = new Map(); - participants.set(membership.member, participantInfoMap); - - for (const [deviceId, participant] of participantsStateMap) { - const feed = groupCall.userMediaFeeds.find( - (f) => f.userId === member.userId && f.deviceId === deviceId - ); - - let connectionState: ConnectionState; - // If we allow calls without media, we have no feeds and cannot read the connection status from them. - // @TODO: The connection state should generally not be determined by the feed. - if ( - groupCall.allowCallWithoutVideoAndAudio && - !feed && - !participant.screensharing - ) { - connectionState = ConnectionState.Connected; - } else { - connectionState = feed - ? feed.connected - ? ConnectionState.Connected - : ConnectionState.WaitMedia - : ConnectionState.EstablishingCall; - } - participantInfoMap.set(deviceId, { - connectionState, - presenter: participant.screensharing, - }); - } - } - - return participants; -}*/ - -export function useGroupCall( - rtcSession: MatrixRTCSession, - client: MatrixClient -): UseGroupCallReturnType { - const [ - { - state, - localCallFeed, - activeSpeaker, - userMediaFeeds, - error, - microphoneMuted, - localVideoMuted, - isScreensharing, - screenshareFeeds, - participants, - hasLocalParticipant, - requestingScreenshare, - }, - setState, - ] = useState({ - state: GroupCallState.LocalCallFeedUninitialized, - userMediaFeeds: [], - microphoneMuted: false, - localVideoMuted: false, - isScreensharing: false, - screenshareFeeds: [], - requestingScreenshare: false, - participants: getParticipants(rtcSession), - hasLocalParticipant: false, - }); - - if (groupCallOTelMembershipGroupCallId !== groupCall.groupCallId) { - if (groupCallOTelMembership) groupCallOTelMembership.dispose(); - - // If the user disables analytics, this will stay around until they leave the call - // so analytics will be disabled once they leave. - if (ElementCallOpenTelemetry.instance) { - groupCallOTelMembership = new OTelGroupCallMembership(groupCall, client); - groupCallOTelMembershipGroupCallId = groupCall.groupCallId; - } else { - groupCallOTelMembership = undefined; - } - } - - const [unencryptedEventsFromUsers, addUnencryptedEventUser] = useReducer( - (state: Set, newVal: string) => { - return new Set(state).add(newVal); - }, - new Set() - ); - - const updateState = useCallback( - (state: Partial) => setState((prev) => ({ ...prev, ...state })), - [setState] - ); - - const doNothingMediaActionCallback = useCallback( - (details: MediaSessionActionDetails) => {}, - [] - ); - - const leaveCall = useCallback(() => { - groupCallOTelMembership?.onLeaveCall(); - groupCall.leave(); - }, [groupCall]); - - useEffect(() => { - // disable the media action keys, otherwise audio elements get paused when - // the user presses media keys or unplugs headphones, etc. - // Note there are actions for muting / unmuting a microphone & hanging up - // which we could wire up. - const mediaActions: MediaSessionAction[] = [ - "play", - "pause", - "stop", - "nexttrack", - "previoustrack", - ]; - - for (const mediaAction of mediaActions) { - navigator.mediaSession?.setActionHandler( - mediaAction, - doNothingMediaActionCallback - ); - } - - return () => { - for (const mediaAction of mediaActions) { - navigator.mediaSession?.setActionHandler(mediaAction, null); - } - }; - }, [doNothingMediaActionCallback]); - - useEffect(() => { - function onGroupCallStateChanged() { - updateState({ - state: groupCall.state, - localCallFeed: groupCall.localCallFeed, - activeSpeaker: groupCall.activeSpeaker, - userMediaFeeds: [...groupCall.userMediaFeeds], - microphoneMuted: groupCall.isMicrophoneMuted(), - localVideoMuted: groupCall.isLocalVideoMuted(), - isScreensharing: groupCall.isScreensharing(), - screenshareFeeds: [...groupCall.screenshareFeeds], - }); - } - - const prevUserMediaFeeds = new Set(); - - function onUserMediaFeedsChanged(userMediaFeeds: CallFeed[]): void { - for (const feed of prevUserMediaFeeds) { - feed.off(CallFeedEvent.ConnectedChanged, onConnectedChanged); - } - prevUserMediaFeeds.clear(); - - for (const feed of userMediaFeeds) { - feed.on(CallFeedEvent.ConnectedChanged, onConnectedChanged); - prevUserMediaFeeds.add(feed); - } - - updateState({ - userMediaFeeds: [...userMediaFeeds], - participants: getParticipants(groupCall), - }); - } - - const prevScreenshareFeeds = new Set(); - - function onScreenshareFeedsChanged(screenshareFeeds: CallFeed[]): void { - for (const feed of prevScreenshareFeeds) { - feed.off(CallFeedEvent.ConnectedChanged, onConnectedChanged); - } - prevScreenshareFeeds.clear(); - - for (const feed of screenshareFeeds) { - feed.on(CallFeedEvent.ConnectedChanged, onConnectedChanged); - prevScreenshareFeeds.add(feed); - } - - updateState({ - screenshareFeeds: [...screenshareFeeds], - }); - } - - function onConnectedChanged(connected: boolean): void { - updateState({ - participants: getParticipants(groupCall), - }); - } - - function onActiveSpeakerChanged(activeSpeaker: CallFeed | undefined): void { - updateState({ - activeSpeaker: activeSpeaker, - }); - } - - function onLocalMuteStateChanged( - microphoneMuted: boolean, - localVideoMuted: boolean - ): void { - updateState({ - microphoneMuted, - localVideoMuted, - }); - } - - function onLocalScreenshareStateChanged( - isScreensharing: boolean, - _localScreenshareFeed?: CallFeed, - localDesktopCapturerSourceId?: string - ): void { - updateState({ - isScreensharing, - }); - } - - function onCallsChanged(): void { - updateState({ participants: getParticipants(groupCall) }); - } - - function onParticipantsChanged(): void { - updateState({ - participants: getParticipants(groupCall), - hasLocalParticipant: groupCall.hasLocalParticipant(), - }); - } - - function onError(e: GroupCallError): void { - Sentry.captureException(e); - if (e.code === GroupCallErrorCode.UnknownDevice) { - const unknownDeviceError = e as GroupCallUnknownDeviceError; - addUnencryptedEventUser(unknownDeviceError.userId); - } - } - - function onConnectionStatsReport( - report: GroupCallStatsReport - ): void { - groupCallOTelMembership?.onConnectionStatsReport(report); - } - - function onByteSentStatsReport( - report: GroupCallStatsReport - ): void { - groupCallOTelMembership?.onByteSentStatsReport(report); - } - - function onSummaryStatsReport( - report: GroupCallStatsReport - ): void { - groupCallOTelMembership?.onSummaryStatsReport(report); - } - - function onCallFeedStatsReport( - report: GroupCallStatsReport - ): void { - groupCallOTelMembership?.onCallFeedStatsReport(report); - } - - groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged); - groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged); - groupCall.on( - GroupCallEvent.ScreenshareFeedsChanged, - onScreenshareFeedsChanged - ); - groupCall.on(GroupCallEvent.ActiveSpeakerChanged, onActiveSpeakerChanged); - groupCall.on(GroupCallEvent.LocalMuteStateChanged, onLocalMuteStateChanged); - groupCall.on( - GroupCallEvent.LocalScreenshareStateChanged, - onLocalScreenshareStateChanged - ); - groupCall.on(GroupCallEvent.CallsChanged, onCallsChanged); - groupCall.on(GroupCallEvent.ParticipantsChanged, onParticipantsChanged); - groupCall.on(GroupCallEvent.Error, onError); - groupCall.on( - GroupCallStatsReportEvent.ConnectionStats, - onConnectionStatsReport - ); - groupCall.on( - GroupCallStatsReportEvent.ByteSentStats, - onByteSentStatsReport - ); - groupCall.on(GroupCallStatsReportEvent.SummaryStats, onSummaryStatsReport); - groupCall.on( - GroupCallStatsReportEvent.CallFeedStats, - onCallFeedStatsReport - ); - - groupCall.room.currentState.on( - RoomStateEvent.Update, - checkForParallelCalls - ); - - updateState({ - error: undefined, - state: groupCall.state, - localCallFeed: groupCall.localCallFeed, - activeSpeaker: groupCall.activeSpeaker, - userMediaFeeds: [...groupCall.userMediaFeeds], - microphoneMuted: groupCall.isMicrophoneMuted(), - localVideoMuted: groupCall.isLocalVideoMuted(), - isScreensharing: groupCall.isScreensharing(), - screenshareFeeds: [...groupCall.screenshareFeeds], - participants: getParticipants(groupCall), - hasLocalParticipant: groupCall.hasLocalParticipant(), - }); - - return () => { - groupCall.removeListener( - GroupCallEvent.GroupCallStateChanged, - onGroupCallStateChanged - ); - groupCall.removeListener( - GroupCallEvent.UserMediaFeedsChanged, - onUserMediaFeedsChanged - ); - groupCall.removeListener( - GroupCallEvent.ScreenshareFeedsChanged, - onScreenshareFeedsChanged - ); - groupCall.removeListener( - GroupCallEvent.ActiveSpeakerChanged, - onActiveSpeakerChanged - ); - groupCall.removeListener( - GroupCallEvent.LocalMuteStateChanged, - onLocalMuteStateChanged - ); - groupCall.removeListener( - GroupCallEvent.LocalScreenshareStateChanged, - onLocalScreenshareStateChanged - ); - groupCall.removeListener(GroupCallEvent.CallsChanged, onCallsChanged); - groupCall.removeListener( - GroupCallEvent.ParticipantsChanged, - onParticipantsChanged - ); - groupCall.removeListener(GroupCallEvent.Error, onError); - groupCall.removeListener( - GroupCallStatsReportEvent.ConnectionStats, - onConnectionStatsReport - ); - groupCall.removeListener( - GroupCallStatsReportEvent.ByteSentStats, - onByteSentStatsReport - ); - groupCall.removeListener( - GroupCallStatsReportEvent.SummaryStats, - onSummaryStatsReport - ); - groupCall.removeListener( - GroupCallStatsReportEvent.CallFeedStats, - onCallFeedStatsReport - ); - groupCall.room.currentState.off( - RoomStateEvent.Update, - checkForParallelCalls - ); - leaveCall(); - }; - }, [groupCall, updateState, leaveCall]); - - usePageUnload(() => { - leaveCall(); - }); - - const initLocalCallFeed = useCallback( - () => groupCall.initLocalCallFeed(), - [groupCall] - ); - - const enter = useCallback(async () => { - if ( - groupCall.state !== GroupCallState.LocalCallFeedUninitialized && - groupCall.state !== GroupCallState.LocalCallFeedInitialized - ) { - return; - } - - PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); - PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId); - - // This must be called before we start trying to join the call, as we need to - // have started tracking by the time calls start getting created. - groupCallOTelMembership?.onJoinCall(); - - await groupCall.enter().catch((error) => { - console.error(error); - updateState({ error }); - }); - }, [groupCall, updateState]); - - const toggleLocalVideoMuted = useCallback(() => { - const toggleToMute = !groupCall.isLocalVideoMuted(); - groupCall.setLocalVideoMuted(toggleToMute); - groupCallOTelMembership?.onToggleLocalVideoMuted(toggleToMute); - // TODO: These explict posthog calls should be unnecessary now with the posthog otel exporter? - PosthogAnalytics.instance.eventMuteCamera.track( - toggleToMute, - groupCall.groupCallId - ); - }, [groupCall]); - - const setMicrophoneMuted = useCallback( - (setMuted: boolean) => { - groupCall.setMicrophoneMuted(setMuted); - groupCallOTelMembership?.onSetMicrophoneMuted(setMuted); - PosthogAnalytics.instance.eventMuteMicrophone.track( - setMuted, - groupCall.groupCallId - ); - }, - [groupCall] - ); - - const toggleMicrophoneMuted = useCallback(() => { - const toggleToMute = !groupCall.isMicrophoneMuted(); - groupCallOTelMembership?.onToggleMicrophoneMuted(toggleToMute); - setMicrophoneMuted(toggleToMute); - }, [groupCall, setMicrophoneMuted]); - - const toggleScreensharing = useCallback(async () => { - groupCallOTelMembership?.onToggleScreensharing(!groupCall.isScreensharing); - - if (!groupCall.isScreensharing()) { - // toggling on - updateState({ requestingScreenshare: true }); - - try { - await groupCall.setScreensharingEnabled(true, { - audio: true, - throwOnFail: true, - }); - updateState({ requestingScreenshare: false }); - } catch (e) { - // this will fail in Electron because getDisplayMedia just throws a permission - // error, so if we have a widget API, try requesting via that. - if (widget) { - const reply = await widget.api.transport.send( - ElementWidgetActions.ScreenshareRequest, - {} - ); - if (!reply.pending) { - updateState({ requestingScreenshare: false }); - } - } - } - } else { - // toggling off - groupCall.setScreensharingEnabled(false); - } - }, [groupCall, updateState]); - - const onScreenshareStart = useCallback( - async (ev: CustomEvent) => { - updateState({ requestingScreenshare: false }); - - const data = ev.detail.data as unknown as ScreenshareStartData; - - await groupCall.setScreensharingEnabled(true, { - desktopCapturerSourceId: data.desktopCapturerSourceId as string, - audio: !data.desktopCapturerSourceId, - }); - await widget?.api.transport.reply(ev.detail, {}); - }, - [groupCall, updateState] - ); - - const onScreenshareStop = useCallback( - async (ev: CustomEvent) => { - updateState({ requestingScreenshare: false }); - await groupCall.setScreensharingEnabled(false); - await widget?.api.transport.reply(ev.detail, {}); - }, - [groupCall, updateState] - ); - - useEffect(() => { - if (widget) { - widget.lazyActions.on( - ElementWidgetActions.ScreenshareStart, - onScreenshareStart - ); - widget.lazyActions.on( - ElementWidgetActions.ScreenshareStop, - onScreenshareStop - ); - - return () => { - widget?.lazyActions.off( - ElementWidgetActions.ScreenshareStart, - onScreenshareStart - ); - widget?.lazyActions.off( - ElementWidgetActions.ScreenshareStop, - onScreenshareStop - ); - }; - } - }, [onScreenshareStart, onScreenshareStop]); - - const { t } = useTranslation(); - - useEffect(() => { - if (window.RTCPeerConnection === undefined) { - const error = translatedError( - "WebRTC is not supported or is being blocked in this browser.", - t - ); - console.error(error); - updateState({ error }); - } - }, [t, updateState]); - - return { - state, - localCallFeed, - activeSpeaker, - userMediaFeeds, - microphoneMuted, - localVideoMuted, - error, - initLocalCallFeed, - enter, - leave: leaveCall, - toggleLocalVideoMuted, - toggleMicrophoneMuted, - toggleScreensharing, - setMicrophoneMuted, - requestingScreenshare, - isScreensharing, - screenshareFeeds, - participants, - hasLocalParticipant, - unencryptedEventsFromUsers, - otelGroupCallMembership: groupCallOTelMembership, - }; -}