mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
use the matrixRtcSdk
This commit is contained in:
+156
-45
@@ -46,7 +46,10 @@ import {
|
||||
} from "../state/CallViewModel/CallViewModel.ts";
|
||||
import { Grid, type TileProps } from "../grid/Grid";
|
||||
import { SpotlightTile } from "../tile/SpotlightTile";
|
||||
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
|
||||
import {
|
||||
type EncryptionSystem,
|
||||
type MatrixRTCMode,
|
||||
} from "@element-hq/matrixrtc-sdk";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
import { makeGridLayout } from "../grid/GridLayout";
|
||||
import { type CallLayoutOutputs } from "../grid/CallLayout";
|
||||
@@ -63,9 +66,36 @@ import {
|
||||
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||
import { ReactionsOverlay } from "./ReactionsOverlay";
|
||||
import { CallEventAudioRenderer } from "./CallEventAudioRenderer";
|
||||
import { matrixRTCMode as matrixRTCModeSetting } from "../settings/settings";
|
||||
import {
|
||||
createMatrixRTCClient,
|
||||
type MatrixRTCClientOptions,
|
||||
constant,
|
||||
type Behavior,
|
||||
} from "@element-hq/matrixrtc-sdk";
|
||||
import { map } from "rxjs";
|
||||
import {
|
||||
advancedCamera,
|
||||
advancedScreenShare,
|
||||
autoGainControlSetting,
|
||||
cameraBitrate,
|
||||
cameraCodec,
|
||||
cameraFramerate,
|
||||
cameraResolution,
|
||||
customLivekitUrl,
|
||||
echoCancellationSetting,
|
||||
matrixRTCMode as matrixRTCModeSetting,
|
||||
noiseSuppressionSetting,
|
||||
parseResolution,
|
||||
screenShareBitrate,
|
||||
screenShareCodec,
|
||||
screenShareFramerate,
|
||||
screenShareResolution,
|
||||
} from "../settings/settings";
|
||||
import { Config } from "../config/Config";
|
||||
import { type MediaDevice } from "../state/MediaDevices";
|
||||
import { createSentCallNotification$ } from "../state/CallViewModel/CallNotificationLifecycle.ts";
|
||||
import { ReactionsReader } from "../reactions/ReactionsReader";
|
||||
import { LivekitRoomAudioRenderer } from "../livekit/MatrixAudioRenderer.tsx";
|
||||
import { MemberAudioRenderer } from "../tracks/MemberAudioRenderer.tsx";
|
||||
import { muteAllAudio$ } from "../state/MuteAllAudioModel.ts";
|
||||
import { useMediaDevices } from "../MediaDevicesContext.ts";
|
||||
import { EarpieceOverlay } from "./EarpieceOverlay.tsx";
|
||||
@@ -76,10 +106,9 @@ import {
|
||||
} from "../AppBar.tsx";
|
||||
import { useBehavior } from "../useBehavior.ts";
|
||||
import { useValueBehavior } from "../useValueBehavior.ts";
|
||||
import { constant } from "../state/Behavior.ts";
|
||||
import { Toast } from "../Toast.tsx";
|
||||
import overlayStyles from "../Overlay.module.css";
|
||||
import { useTrackProcessorState$ } from "../livekit/TrackProcessorContext.tsx";
|
||||
import { useTrackProcessorState$ } from "../tracks/TrackProcessorContext.tsx";
|
||||
import { type Layout } from "../state/layout-types.ts";
|
||||
import { ObservableScope } from "../state/ObservableScope.ts";
|
||||
import { CallFooter, type FooterSnapshot } from "../components/CallFooter.tsx";
|
||||
@@ -100,7 +129,7 @@ declare module "react" {
|
||||
|
||||
export interface ActiveCallProps extends Omit<
|
||||
InCallViewProps,
|
||||
"vm" | "livekitRoom" | "connState" | "footerVm" | "developerSettingsVm"
|
||||
"vm" | "footerVm" | "developerSettingsVm"
|
||||
> {
|
||||
e2eeSystem: EncryptionSystem;
|
||||
// TODO refactor those reasons into an enum
|
||||
@@ -131,9 +160,28 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
const { autoLeaveWhenOthersLeft, waitForCallPickup, sendNotificationType } =
|
||||
urlParams;
|
||||
|
||||
const rtcClient = createMatrixRTCClient(
|
||||
scope,
|
||||
props.client,
|
||||
props.matrixRoom,
|
||||
{
|
||||
microphoneEnabled: props.muteStates.audio.enabled$.value,
|
||||
cameraEnabled: props.muteStates.video.enabled$.value,
|
||||
audioInputDeviceId$: selectedDeviceId$(scope, mediaDevices.audioInput),
|
||||
videoInputDeviceId$: selectedDeviceId$(scope, mediaDevices.videoInput),
|
||||
// A host that routes audio itself leaves the browser's output alone
|
||||
audioOutputDeviceId$: urlParams.controlledAudioDevices
|
||||
? constant(undefined)
|
||||
: selectedDeviceId$(scope, mediaDevices.audioOutput),
|
||||
videoProcessor$: scope.behavior(
|
||||
trackProcessorState$.pipe(map((state) => state.processor)),
|
||||
),
|
||||
},
|
||||
matrixRTCClientOptions(urlParams, props.e2eeSystem),
|
||||
);
|
||||
const vm = createCallViewModel$(
|
||||
scope,
|
||||
props.rtcSession,
|
||||
rtcClient,
|
||||
props.matrixRoom,
|
||||
mediaDevices,
|
||||
props.muteStates,
|
||||
@@ -143,14 +191,14 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
hostBridge,
|
||||
autoLeaveWhenOthersLeft,
|
||||
waitForCallPickup: waitForCallPickup && sendNotificationType === "ring",
|
||||
// We merely sample the current mode here, so the user would need to
|
||||
// manually rejoin to switch to a different one.
|
||||
matrixRTCMode: matrixRTCModeSetting.value$.value,
|
||||
sentCallNotification$: createSentCallNotification$(
|
||||
scope,
|
||||
props.rtcSession,
|
||||
),
|
||||
windowSize$: scope.behavior(observeElementSize$(rootElement)),
|
||||
},
|
||||
reactionsReader.raisedHands$,
|
||||
reactionsReader.reactions$,
|
||||
trackProcessorState$,
|
||||
);
|
||||
// TODO move this somewhere else once we use the callViewModel in the lobby as well!
|
||||
vm.join();
|
||||
@@ -250,16 +298,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
const { sendReaction, toggleRaisedHand } = useReactionsSender();
|
||||
|
||||
useWakeLock();
|
||||
// TODO-MULTI-SFU This is unused now??
|
||||
// const connectionState = useBehavior(vm.livekitConnectionState$);
|
||||
|
||||
// annoyingly we don't get the disconnection reason this way,
|
||||
// only by listening for the emitted event
|
||||
// This needs to be done differential. with the vm connection state we start with Disconnected.
|
||||
// TODO-MULTI-SFU decide how to handle this properly
|
||||
// @BillCarsonFr
|
||||
// if (connectionState === ConnectionState.Disconnected)
|
||||
// throw new ConnectionLostError();
|
||||
|
||||
const containerRef1 = useRef<HTMLDivElement | null>(null);
|
||||
const [containerRef2, bounds] = useMeasure();
|
||||
@@ -296,7 +334,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
);
|
||||
|
||||
const ringingVm = useBehavior(vm.ringingVm$);
|
||||
const audioParticipants = useBehavior(vm.livekitRoomItems$);
|
||||
const remoteMembers = useBehavior(vm.remoteMembers$);
|
||||
const participantCount = useBehavior(vm.participantCount$);
|
||||
const reconnecting = useBehavior(vm.reconnecting$);
|
||||
const screenShareError = useBehavior(vm.screenShareError$);
|
||||
@@ -646,7 +684,11 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
const footer = footerVm !== null && (
|
||||
<CallFooter className={styles.footer} ref={footerRef} vm={footerVm} />
|
||||
);
|
||||
const allConnections = useBehavior(vm.allConnections$);
|
||||
const connectedTransports = useBehavior(vm.connectedTransports$);
|
||||
const localMember = useBehavior(vm.localMember$);
|
||||
const localTransport = useBehavior(
|
||||
localMember?.transport$ ?? constant(undefined),
|
||||
);
|
||||
|
||||
return (
|
||||
// The pointer handler here exists to control the visibility of the footer,
|
||||
@@ -665,15 +707,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
onPointerOut={onPointerOut}
|
||||
>
|
||||
{header}
|
||||
{audioParticipants.map(({ livekitRoom, transport, participants }) => (
|
||||
<LivekitRoomAudioRenderer
|
||||
key={JSON.stringify(transport)}
|
||||
transport={transport}
|
||||
livekitRoom={livekitRoom}
|
||||
validIdentities={participants}
|
||||
muted={muteAllAudio}
|
||||
/>
|
||||
))}
|
||||
<MemberAudioRenderer members={remoteMembers} muted={muteAllAudio} />
|
||||
{renderContent()}
|
||||
<CallEventAudioRenderer vm={vm} muted={muteAllAudio} />
|
||||
<ReactionsAudioRenderer vm={vm} muted={muteAllAudio} />
|
||||
@@ -694,22 +728,99 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
tab={settingsTab}
|
||||
onTabChange={setSettingsTab}
|
||||
developerSettingsVm={developerSettingsVm}
|
||||
livekitRooms={allConnections.map(
|
||||
({ livekitRoom: room, livekitAlias, transport, role }) => ({
|
||||
room,
|
||||
livekitAlias,
|
||||
role,
|
||||
url:
|
||||
// It should be relatively clear to the user which kind of URL
|
||||
// this is, based on the scheme (ws:// vs. http://)
|
||||
"url" in transport
|
||||
? transport.url
|
||||
: transport.livekit_service_url,
|
||||
}),
|
||||
)}
|
||||
transports={connectedTransports.map((transport) => ({
|
||||
transport,
|
||||
local: transport === localTransport,
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** The id of the device the user picked, for the client to capture or play on. */
|
||||
export function selectedDeviceId$(
|
||||
scope: ObservableScope,
|
||||
device: MediaDevice<unknown, { id: string }>,
|
||||
): Behavior<string | undefined> {
|
||||
return scope.behavior(device.selected$.pipe(map((d) => d?.id)));
|
||||
}
|
||||
|
||||
/**
|
||||
* What the client needs from Element Call's configuration, settings and URL:
|
||||
* the SDK reads none of them itself. Shared with the legacy SDK bundle so that
|
||||
* a widget built from it behaves like the app.
|
||||
*/
|
||||
export function matrixRTCClientOptions(
|
||||
urlParams: ReturnType<typeof useUrlParams>,
|
||||
encryptionSystem: EncryptionSystem,
|
||||
): MatrixRTCClientOptions {
|
||||
const config = Config.get();
|
||||
const session = config.matrix_rtc_session;
|
||||
const cameraSettings = advancedCamera.getValue()
|
||||
? {
|
||||
resolution: {
|
||||
...parseResolution(cameraResolution.getValue()),
|
||||
frameRate: cameraFramerate.getValue(),
|
||||
},
|
||||
maxBitrate: cameraBitrate.getValue(),
|
||||
maxFramerate: cameraFramerate.getValue(),
|
||||
codec: cameraCodec.getValue(),
|
||||
}
|
||||
: undefined;
|
||||
const screenConfig = config.media_quality?.screen_share;
|
||||
const screenShareSettings = advancedScreenShare.getValue()
|
||||
? {
|
||||
resolution: {
|
||||
...parseResolution(screenShareResolution.getValue()),
|
||||
frameRate: screenShareFramerate.getValue(),
|
||||
},
|
||||
maxBitrate: screenShareBitrate.getValue(),
|
||||
maxFramerate: screenShareFramerate.getValue(),
|
||||
codec: screenShareCodec.getValue(),
|
||||
}
|
||||
: screenConfig?.max_resolution
|
||||
? {
|
||||
resolution: {
|
||||
width: Math.round((screenConfig.max_resolution * 16) / 9),
|
||||
height: screenConfig.max_resolution,
|
||||
frameRate: screenConfig.max_framerate ?? 30,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
return {
|
||||
encryptionSystem,
|
||||
// matrix_rtc_mode in config.json overrides the user's Developer Settings
|
||||
// choice. We merely sample the current mode here, so the user would need
|
||||
// to manually rejoin to switch to a different one.
|
||||
matrixRTCMode:
|
||||
(config.matrix_rtc_mode as MatrixRTCMode | undefined) ??
|
||||
matrixRTCModeSetting.value$.value,
|
||||
sendNotificationType: urlParams.sendNotificationType,
|
||||
applicationData: urlParams.callIntent
|
||||
? { "m.call.intent": urlParams.callIntent }
|
||||
: undefined,
|
||||
timings: {
|
||||
syncDisconnectGracePeriodMs: config.sync_disconnect_grace_period_ms,
|
||||
networkErrorRetryMs: session.network_error_retry_ms,
|
||||
waitForKeyRotationMs: session.wait_for_key_rotation_ms,
|
||||
membershipEventExpiryMs: session.membership_event_expiry_ms,
|
||||
keyRotationParticipantLimit: session.key_rotation_participant_limit,
|
||||
delayedLeave: session.delayed_leave,
|
||||
delegatedDelayedLeave: session.delegated_delayed_leave,
|
||||
},
|
||||
mediaQuality: config.media_quality,
|
||||
capture: {
|
||||
audio: {
|
||||
echoCancellation: echoCancellationSetting.getValue(),
|
||||
noiseSuppression: noiseSuppressionSetting.getValue(),
|
||||
autoGainControl: autoGainControlSetting.getValue(),
|
||||
},
|
||||
camera: cameraSettings,
|
||||
screenShare: screenShareSettings,
|
||||
},
|
||||
transportUrl: customLivekitUrl.value$.value ?? undefined,
|
||||
fallbackTransportUrl: config.livekit?.livekit_service_url,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user