Merge branch 'main' into livekit-experiment
This commit is contained in:
@@ -22,16 +22,28 @@ import {
|
||||
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 { usePageUnload } from "./usePageUnload";
|
||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||
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
|
||||
@@ -53,7 +65,7 @@ interface UseGroupCallReturnType {
|
||||
localVideoMuted: boolean;
|
||||
error: TranslatedError | null;
|
||||
initLocalCallFeed: () => void;
|
||||
enter: () => void;
|
||||
enter: () => Promise<void>;
|
||||
leave: () => void;
|
||||
toggleLocalVideoMuted: () => void;
|
||||
toggleMicrophoneMuted: () => void;
|
||||
@@ -66,6 +78,7 @@ interface UseGroupCallReturnType {
|
||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||
hasLocalParticipant: boolean;
|
||||
unencryptedEventsFromUsers: Set<string>;
|
||||
otelGroupCallMembership: OTelGroupCallMembership;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -84,6 +97,13 @@ interface State {
|
||||
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;
|
||||
let groupCallOTelMembershipGroupCallId: string;
|
||||
|
||||
function getParticipants(
|
||||
groupCall: GroupCall
|
||||
): Map<RoomMember, Map<string, ParticipantInfo>> {
|
||||
@@ -98,12 +118,24 @@ function getParticipants(
|
||||
(f) => f.userId === member.userId && f.deviceId === deviceId
|
||||
);
|
||||
|
||||
participantInfoMap.set(deviceId, {
|
||||
connectionState: feed
|
||||
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,
|
||||
: ConnectionState.EstablishingCall;
|
||||
}
|
||||
participantInfoMap.set(deviceId, {
|
||||
connectionState,
|
||||
presenter: participant.screensharing,
|
||||
});
|
||||
}
|
||||
@@ -112,7 +144,10 @@ function getParticipants(
|
||||
return participants;
|
||||
}
|
||||
|
||||
export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
export function useGroupCall(
|
||||
groupCall: GroupCall,
|
||||
client: MatrixClient
|
||||
): UseGroupCallReturnType {
|
||||
const [
|
||||
{
|
||||
state,
|
||||
@@ -146,6 +181,19 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
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<string>, newVal: string) => {
|
||||
return new Set(state).add(newVal);
|
||||
@@ -158,6 +206,43 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
[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({
|
||||
@@ -261,6 +346,30 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
}
|
||||
}
|
||||
|
||||
function onConnectionStatsReport(
|
||||
report: GroupCallStatsReport<ConnectionStatsReport>
|
||||
): void {
|
||||
groupCallOTelMembership?.onConnectionStatsReport(report);
|
||||
}
|
||||
|
||||
function onByteSentStatsReport(
|
||||
report: GroupCallStatsReport<ByteSentStatsReport>
|
||||
): void {
|
||||
groupCallOTelMembership?.onByteSentStatsReport(report);
|
||||
}
|
||||
|
||||
function onSummaryStatsReport(
|
||||
report: GroupCallStatsReport<SummaryStatsReport>
|
||||
): void {
|
||||
groupCallOTelMembership?.onSummaryStatsReport(report);
|
||||
}
|
||||
|
||||
function onCallFeedStatsReport(
|
||||
report: GroupCallStatsReport<CallFeedReport>
|
||||
): void {
|
||||
groupCallOTelMembership?.onCallFeedStatsReport(report);
|
||||
}
|
||||
|
||||
groupCall.on(GroupCallEvent.GroupCallStateChanged, onGroupCallStateChanged);
|
||||
groupCall.on(GroupCallEvent.UserMediaFeedsChanged, onUserMediaFeedsChanged);
|
||||
groupCall.on(
|
||||
@@ -276,6 +385,24 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
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: null,
|
||||
@@ -323,12 +450,32 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
onParticipantsChanged
|
||||
);
|
||||
groupCall.removeListener(GroupCallEvent.Error, onError);
|
||||
groupCall.leave();
|
||||
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]);
|
||||
}, [groupCall, updateState, leaveCall]);
|
||||
|
||||
usePageUnload(() => {
|
||||
groupCall.leave();
|
||||
leaveCall();
|
||||
});
|
||||
|
||||
const initLocalCallFeed = useCallback(
|
||||
@@ -336,7 +483,7 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
[groupCall]
|
||||
);
|
||||
|
||||
const enter = useCallback(() => {
|
||||
const enter = useCallback(async () => {
|
||||
if (
|
||||
groupCall.state !== GroupCallState.LocalCallFeedUninitialized &&
|
||||
groupCall.state !== GroupCallState.LocalCallFeedInitialized
|
||||
@@ -347,17 +494,21 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
||||
|
||||
groupCall.enter().catch((error) => {
|
||||
// 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 leave = useCallback(() => groupCall.leave(), [groupCall]);
|
||||
|
||||
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
|
||||
@@ -367,6 +518,7 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
const setMicrophoneMuted = useCallback(
|
||||
(setMuted) => {
|
||||
groupCall.setMicrophoneMuted(setMuted);
|
||||
groupCallOTelMembership?.onSetMicrophoneMuted(setMuted);
|
||||
PosthogAnalytics.instance.eventMuteMicrophone.track(
|
||||
setMuted,
|
||||
groupCall.groupCallId
|
||||
@@ -377,10 +529,13 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
|
||||
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 });
|
||||
@@ -481,7 +636,7 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
error,
|
||||
initLocalCallFeed,
|
||||
enter,
|
||||
leave,
|
||||
leave: leaveCall,
|
||||
toggleLocalVideoMuted,
|
||||
toggleMicrophoneMuted,
|
||||
toggleScreensharing,
|
||||
@@ -493,5 +648,6 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType {
|
||||
participants,
|
||||
hasLocalParticipant,
|
||||
unencryptedEventsFromUsers,
|
||||
otelGroupCallMembership: groupCallOTelMembership,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user