mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-02 19:49:23 +00:00
Merge pull request #3882 from element-hq/local-screen-share
Fix local screen share not appearing in one-on-one calls
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
|||||||
|
|
||||||
import { mockConfig } from "./utils/test";
|
import { mockConfig } from "./utils/test";
|
||||||
|
|
||||||
const sentryInitSpy = vi.fn();
|
const sentryInitSpy = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
// Place the mock after the spy is defined
|
// Place the mock after the spy is defined
|
||||||
vi.mock("@sentry/react", () => ({
|
vi.mock("@sentry/react", () => ({
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import { deepCompare } from "matrix-js-sdk/lib/utils";
|
|||||||
|
|
||||||
import { type Layout } from "../layout-types.ts";
|
import { type Layout } from "../layout-types.ts";
|
||||||
import {
|
import {
|
||||||
mockLocalParticipant,
|
|
||||||
mockMatrixRoomMember,
|
mockMatrixRoomMember,
|
||||||
mockRemoteParticipant,
|
mockRemoteParticipant,
|
||||||
withTestScheduler,
|
withTestScheduler,
|
||||||
@@ -61,7 +60,10 @@ import {
|
|||||||
import { MediaDevices } from "../MediaDevices.ts";
|
import { MediaDevices } from "../MediaDevices.ts";
|
||||||
import { getValue } from "../../utils/observable.ts";
|
import { getValue } from "../../utils/observable.ts";
|
||||||
import { type Behavior, constant } from "../Behavior.ts";
|
import { type Behavior, constant } from "../Behavior.ts";
|
||||||
import { withCallViewModel as withCallViewModelInMode } from "./CallViewModelTestUtils.ts";
|
import {
|
||||||
|
localParticipant,
|
||||||
|
withCallViewModel as withCallViewModelInMode,
|
||||||
|
} from "./CallViewModelTestUtils.ts";
|
||||||
import { MatrixRTCMode } from "../../settings/settings.ts";
|
import { MatrixRTCMode } from "../../settings/settings.ts";
|
||||||
import { initializeWidget } from "../../widget.ts";
|
import { initializeWidget } from "../../widget.ts";
|
||||||
|
|
||||||
@@ -104,16 +106,7 @@ const dave = mockMatrixRoomMember(daveRtcMember, { rawDisplayName: "Dave" });
|
|||||||
|
|
||||||
const daveId = `${dave.userId}:${daveRtcMember.deviceId}`;
|
const daveId = `${dave.userId}:${daveRtcMember.deviceId}`;
|
||||||
|
|
||||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
|
||||||
const aliceSharingScreen = mockRemoteParticipant({
|
|
||||||
identity: aliceId,
|
|
||||||
isScreenShareEnabled: true,
|
|
||||||
});
|
|
||||||
const bobParticipant = mockRemoteParticipant({ identity: bobId });
|
const bobParticipant = mockRemoteParticipant({ identity: bobId });
|
||||||
const bobSharingScreen = mockRemoteParticipant({
|
|
||||||
identity: bobId,
|
|
||||||
isScreenShareEnabled: true,
|
|
||||||
});
|
|
||||||
const daveParticipant = mockRemoteParticipant({ identity: daveId });
|
const daveParticipant = mockRemoteParticipant({ identity: daveId });
|
||||||
|
|
||||||
export interface GridLayoutSummary {
|
export interface GridLayoutSummary {
|
||||||
@@ -277,11 +270,12 @@ describe.each([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("screen sharing activates spotlight layout", () => {
|
test("remote screen sharing activates spotlight layout", () => {
|
||||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||||
// Start with no screen shares, then have Alice and Bob share their screens,
|
// Start with no screen shares, then have Alice and Bob share their screens,
|
||||||
// then return to no screen shares, then have just Alice share for a bit
|
// then return to no screen shares, then have just Alice share for a bit
|
||||||
const participantInputMarbles = " abcda-ba";
|
const aliceSharingInputMarbles = " ny-n--yn";
|
||||||
|
const bobSharingInputMarbles = " n-y-n---";
|
||||||
// While there are no screen shares, switch to spotlight manually, and then
|
// While there are no screen shares, switch to spotlight manually, and then
|
||||||
// switch back to grid at the end
|
// switch back to grid at the end
|
||||||
const modeInputMarbles = " -----s--g";
|
const modeInputMarbles = " -----s--g";
|
||||||
@@ -292,13 +286,12 @@ describe.each([
|
|||||||
const expectedShowSpeakingMarbles = "y----nyny";
|
const expectedShowSpeakingMarbles = "y----nyny";
|
||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: behavior(participantInputMarbles, {
|
remoteParticipants$: constant([aliceParticipant, bobParticipant]),
|
||||||
a: [aliceParticipant, bobParticipant],
|
|
||||||
b: [aliceSharingScreen, bobParticipant],
|
|
||||||
c: [aliceSharingScreen, bobSharingScreen],
|
|
||||||
d: [aliceParticipant, bobSharingScreen],
|
|
||||||
}),
|
|
||||||
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[aliceParticipant, behavior(aliceSharingInputMarbles, yesNo)],
|
||||||
|
[bobParticipant, behavior(bobSharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
schedule(modeInputMarbles, {
|
schedule(modeInputMarbles, {
|
||||||
@@ -358,6 +351,76 @@ describe.each([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("local screen sharing stays in grid layout", () => {
|
||||||
|
withTestScheduler(({ behavior, expectObservable }) => {
|
||||||
|
// Local participant shares their screen, then stops sharing
|
||||||
|
const sharingInputMarbles = " nyn";
|
||||||
|
// Layout should show the screen share but stay in type: "grid"
|
||||||
|
const expectedLayoutMarbles = "aba";
|
||||||
|
withCallViewModel(
|
||||||
|
{
|
||||||
|
remoteParticipants$: constant([aliceParticipant, bobParticipant]),
|
||||||
|
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[localParticipant, behavior(sharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
(vm) => {
|
||||||
|
expectObservable(summarizeLayout$(vm.layout$)).toBe(
|
||||||
|
expectedLayoutMarbles,
|
||||||
|
{
|
||||||
|
a: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: undefined,
|
||||||
|
grid: [`${localId}:0`, `${aliceId}:0`, `${bobId}:0`],
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: [`${localId}:0:screen-share`],
|
||||||
|
grid: [`${localId}:0`, `${aliceId}:0`, `${bobId}:0`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("local screen sharing in one-on-one call activates grid layout", () => {
|
||||||
|
withTestScheduler(({ behavior, expectObservable }) => {
|
||||||
|
// Local participant shares their screen, then stops sharing
|
||||||
|
const sharingInputMarbles = " nyn";
|
||||||
|
// Layout should switch to grid layout then back to one-on-one layout
|
||||||
|
const expectedLayoutMarbles = "aba";
|
||||||
|
withCallViewModel(
|
||||||
|
{
|
||||||
|
remoteParticipants$: constant([aliceParticipant]),
|
||||||
|
rtcMembers$: constant([localRtcMember, aliceRtcMember]),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[localParticipant, behavior(sharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
(vm) => {
|
||||||
|
expectObservable(summarizeLayout$(vm.layout$)).toBe(
|
||||||
|
expectedLayoutMarbles,
|
||||||
|
{
|
||||||
|
a: {
|
||||||
|
type: "one-on-one",
|
||||||
|
pip: `${localId}:0`,
|
||||||
|
spotlight: `${aliceId}:0`,
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
type: "grid",
|
||||||
|
spotlight: [`${localId}:0:screen-share`],
|
||||||
|
grid: [`${localId}:0`, `${aliceId}:0`],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("participants stay in the same order unless to appear/disappear", () => {
|
test("participants stay in the same order unless to appear/disappear", () => {
|
||||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||||
const visibilityInputMarbles = "a";
|
const visibilityInputMarbles = "a";
|
||||||
@@ -688,7 +751,7 @@ describe.each([
|
|||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: constant([
|
remoteParticipants$: constant([
|
||||||
aliceSharingScreen,
|
aliceParticipant,
|
||||||
bobParticipant,
|
bobParticipant,
|
||||||
daveParticipant,
|
daveParticipant,
|
||||||
]),
|
]),
|
||||||
@@ -702,6 +765,7 @@ describe.each([
|
|||||||
[bobParticipant, behavior(bSpeakingInputMarbles, yesNo)],
|
[bobParticipant, behavior(bSpeakingInputMarbles, yesNo)],
|
||||||
[daveParticipant, behavior(dSpeakingInputMarbles, yesNo)],
|
[daveParticipant, behavior(dSpeakingInputMarbles, yesNo)],
|
||||||
]),
|
]),
|
||||||
|
sharingScreen: new Map([[aliceParticipant, constant(true)]]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
schedule(modeInputMarbles, {
|
schedule(modeInputMarbles, {
|
||||||
@@ -856,26 +920,30 @@ describe.each([
|
|||||||
withTestScheduler(({ behavior, expectObservable }) => {
|
withTestScheduler(({ behavior, expectObservable }) => {
|
||||||
// iterate through a number of combinations of participants and MatrixRTC memberships
|
// iterate through a number of combinations of participants and MatrixRTC memberships
|
||||||
// Bob never has an MatrixRTC membership
|
// Bob never has an MatrixRTC membership
|
||||||
const scenarioInputMarbles = " abcdec";
|
const participantInputMarbles = "abcd-c";
|
||||||
|
// Bob even tries to share his screen at the end
|
||||||
|
const bobSharingInputMarbles = " n---yn";
|
||||||
// Bob should never be visible
|
// Bob should never be visible
|
||||||
const expectedLayoutMarbles = "a-bc-b";
|
const expectedLayoutMarbles = " a-bc-b";
|
||||||
|
|
||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: behavior(scenarioInputMarbles, {
|
remoteParticipants$: behavior(participantInputMarbles, {
|
||||||
a: [],
|
a: [],
|
||||||
b: [bobParticipant],
|
b: [bobParticipant],
|
||||||
c: [aliceParticipant, bobParticipant],
|
c: [aliceParticipant, bobParticipant],
|
||||||
d: [aliceParticipant, daveParticipant, bobParticipant],
|
d: [aliceParticipant, daveParticipant, bobParticipant],
|
||||||
e: [aliceParticipant, daveParticipant, bobSharingScreen],
|
|
||||||
}),
|
}),
|
||||||
rtcMembers$: behavior(scenarioInputMarbles, {
|
rtcMembers$: behavior(participantInputMarbles, {
|
||||||
a: [localRtcMember],
|
a: [localRtcMember],
|
||||||
b: [localRtcMember],
|
b: [localRtcMember],
|
||||||
c: [localRtcMember, aliceRtcMember],
|
c: [localRtcMember, aliceRtcMember],
|
||||||
d: [localRtcMember, aliceRtcMember, daveRtcMember],
|
d: [localRtcMember, aliceRtcMember, daveRtcMember],
|
||||||
e: [localRtcMember, aliceRtcMember, daveRtcMember],
|
e: [localRtcMember, aliceRtcMember, daveRtcMember],
|
||||||
}),
|
}),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[bobParticipant, behavior(bobSharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
vm.setGridMode("grid");
|
vm.setGridMode("grid");
|
||||||
|
|||||||
@@ -105,12 +105,16 @@ import {
|
|||||||
import {
|
import {
|
||||||
createLocalTransport$,
|
createLocalTransport$,
|
||||||
JwtEndpointVersion,
|
JwtEndpointVersion,
|
||||||
|
type LocalTransport,
|
||||||
} from "./localMember/LocalTransport.ts";
|
} from "./localMember/LocalTransport.ts";
|
||||||
import {
|
import {
|
||||||
createMemberships$,
|
createMemberships$,
|
||||||
membershipsAndTransports$,
|
membershipsAndTransports$,
|
||||||
} from "../SessionBehaviors.ts";
|
} from "../SessionBehaviors.ts";
|
||||||
import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts";
|
import {
|
||||||
|
type ConnectionFactory,
|
||||||
|
ECConnectionFactory,
|
||||||
|
} from "./remoteMembers/ConnectionFactory.ts";
|
||||||
import {
|
import {
|
||||||
type ConnectionManagerData,
|
type ConnectionManagerData,
|
||||||
createConnectionManager$,
|
createConnectionManager$,
|
||||||
@@ -170,6 +174,10 @@ export interface CallViewModelOptions {
|
|||||||
connectionState$?: Behavior<ConnectionState>;
|
connectionState$?: Behavior<ConnectionState>;
|
||||||
/** Optional behavior overriding the computed window size, mainly for testing purposes. */
|
/** Optional behavior overriding the computed window size, mainly for testing purposes. */
|
||||||
windowSize$?: Behavior<{ width: number; height: number }>;
|
windowSize$?: Behavior<{ width: number; height: number }>;
|
||||||
|
/** Optional value overriding the local transport, for testing purposes. */
|
||||||
|
localTransport?: LocalTransport;
|
||||||
|
/** Optional value overriding the connection factory, for testing purposes. */
|
||||||
|
connectionFactory?: ConnectionFactory;
|
||||||
/** The version & compatibility mode of MatrixRTC that we should use. */
|
/** The version & compatibility mode of MatrixRTC that we should use. */
|
||||||
matrixRTCMode$?: Behavior<MatrixRTCMode>;
|
matrixRTCMode$?: Behavior<MatrixRTCMode>;
|
||||||
}
|
}
|
||||||
@@ -441,6 +449,7 @@ export function createCallViewModel$(
|
|||||||
// Re-create LocalTransport whenever the mode changes
|
// Re-create LocalTransport whenever the mode changes
|
||||||
(mode) => ({ keys: [mode], data: undefined }),
|
(mode) => ({ keys: [mode], data: undefined }),
|
||||||
(scope, _data$, mode) =>
|
(scope, _data$, mode) =>
|
||||||
|
options.localTransport ??
|
||||||
createLocalTransport$({
|
createLocalTransport$({
|
||||||
scope: scope,
|
scope: scope,
|
||||||
memberships$: memberships$,
|
memberships$: memberships$,
|
||||||
@@ -467,17 +476,19 @@ export function createCallViewModel$(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectionFactory = new ECConnectionFactory(
|
const connectionFactory =
|
||||||
client,
|
options.connectionFactory ??
|
||||||
matrixRoom.roomId,
|
new ECConnectionFactory(
|
||||||
mediaDevices,
|
client,
|
||||||
trackProcessorState$,
|
matrixRoom.roomId,
|
||||||
livekitKeyProvider,
|
mediaDevices,
|
||||||
getUrlParams().controlledAudioDevices,
|
trackProcessorState$,
|
||||||
options.livekitRoomFactory,
|
livekitKeyProvider,
|
||||||
getUrlParams().echoCancellation,
|
getUrlParams().controlledAudioDevices,
|
||||||
getUrlParams().noiseSuppression,
|
options.livekitRoomFactory,
|
||||||
);
|
getUrlParams().echoCancellation,
|
||||||
|
getUrlParams().noiseSuppression,
|
||||||
|
);
|
||||||
|
|
||||||
const connectionManager = createConnectionManager$({
|
const connectionManager = createConnectionManager$({
|
||||||
scope: scope,
|
scope: scope,
|
||||||
@@ -1078,9 +1089,10 @@ export function createCallViewModel$(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const oneOnOneLayoutMedia$: Observable<OneOnOneLayoutMedia | null> =
|
const oneOnOneLayoutMedia$: Observable<OneOnOneLayoutMedia | null> =
|
||||||
userMedia$.pipe(
|
combineLatest([userMedia$, screenShares$]).pipe(
|
||||||
switchMap((userMedia) => {
|
switchMap(([userMedia, screenShares]) => {
|
||||||
if (userMedia.length <= 2) {
|
// One-on-one layout only supports 2 user media, no screen shares
|
||||||
|
if (userMedia.length <= 2 && screenShares.length === 0) {
|
||||||
const local = userMedia.find(
|
const local = userMedia.find(
|
||||||
(vm): vm is WrappedUserMediaViewModel & LocalUserMediaViewModel =>
|
(vm): vm is WrappedUserMediaViewModel & LocalUserMediaViewModel =>
|
||||||
vm.type === "user" && vm.local,
|
vm.type === "user" && vm.local,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
type Room as LivekitRoom,
|
type Room as LivekitRoom,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { SyncState } from "matrix-js-sdk/lib/sync";
|
import { SyncState } from "matrix-js-sdk/lib/sync";
|
||||||
import { BehaviorSubject, type Observable, map, of } from "rxjs";
|
import { BehaviorSubject, combineLatest, map, of } from "rxjs";
|
||||||
import { onTestFinished, vi } from "vitest";
|
import { onTestFinished, vi } from "vitest";
|
||||||
import { ClientEvent, type RoomMember, type MatrixClient } from "matrix-js-sdk";
|
import { ClientEvent, type RoomMember, type MatrixClient } from "matrix-js-sdk";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
@@ -30,7 +30,10 @@ import {
|
|||||||
type CallViewModelOptions,
|
type CallViewModelOptions,
|
||||||
} from "./CallViewModel";
|
} from "./CallViewModel";
|
||||||
import {
|
import {
|
||||||
|
exampleSfuConfig,
|
||||||
|
exampleTransport,
|
||||||
mockConfig,
|
mockConfig,
|
||||||
|
MockConnection,
|
||||||
mockLivekitRoom,
|
mockLivekitRoom,
|
||||||
mockLocalParticipant,
|
mockLocalParticipant,
|
||||||
mockMatrixRoom,
|
mockMatrixRoom,
|
||||||
@@ -68,13 +71,14 @@ export interface CallViewModelInputs {
|
|||||||
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
||||||
roomMembers: RoomMember[];
|
roomMembers: RoomMember[];
|
||||||
livekitConnectionState$: Behavior<ConnectionState>;
|
livekitConnectionState$: Behavior<ConnectionState>;
|
||||||
speaking: Map<Participant, Observable<boolean>>;
|
speaking: Map<Participant, Behavior<boolean>>;
|
||||||
|
sharingScreen: Map<Participant, Behavior<boolean>>;
|
||||||
mediaDevices: MediaDevices;
|
mediaDevices: MediaDevices;
|
||||||
initialSyncState: SyncState;
|
initialSyncState: SyncState;
|
||||||
windowSize$: Behavior<{ width: number; height: number }>;
|
windowSize$: Behavior<{ width: number; height: number }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
export const localParticipant = mockLocalParticipant({ identity: "" });
|
||||||
|
|
||||||
export function withCallViewModel(mode: MatrixRTCMode) {
|
export function withCallViewModel(mode: MatrixRTCMode) {
|
||||||
return (
|
return (
|
||||||
@@ -94,6 +98,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
ConnectionState.Connected,
|
ConnectionState.Connected,
|
||||||
),
|
),
|
||||||
speaking = new Map(),
|
speaking = new Map(),
|
||||||
|
sharingScreen = new Map(),
|
||||||
mediaDevices = mockMediaDevices({}),
|
mediaDevices = mockMediaDevices({}),
|
||||||
initialSyncState = SyncState.Syncing,
|
initialSyncState = SyncState.Syncing,
|
||||||
windowSize$ = constant({ width: 1000, height: 800 }),
|
windowSize$ = constant({ width: 1000, height: 800 }),
|
||||||
@@ -154,13 +159,19 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
const eventsSpy = vi
|
const eventsSpy = vi
|
||||||
.spyOn(ComponentsCore, "observeParticipantEvents")
|
.spyOn(ComponentsCore, "observeParticipantEvents")
|
||||||
.mockImplementation((p, ...eventTypes) => {
|
.mockImplementation((p, ...eventTypes) => {
|
||||||
if (eventTypes.includes(ParticipantEvent.IsSpeakingChanged)) {
|
return combineLatest([
|
||||||
return (speaking.get(p) ?? of(false)).pipe(
|
(eventTypes.includes(ParticipantEvent.IsSpeakingChanged) &&
|
||||||
map((s): Participant => ({ ...p, isSpeaking: s }) as Participant),
|
speaking.get(p)) ||
|
||||||
);
|
constant(false),
|
||||||
} else {
|
(eventTypes.includes(ParticipantEvent.TrackPublished) &&
|
||||||
return of(p);
|
sharingScreen.get(p)) ||
|
||||||
}
|
constant(false),
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([isSpeaking, isScreenShareEnabled]) =>
|
||||||
|
({ ...p, isSpeaking, isScreenShareEnabled }) as Participant,
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const roomEventSelectorSpy = vi
|
const roomEventSelectorSpy = vi
|
||||||
@@ -172,6 +183,13 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
);
|
);
|
||||||
const reactions$ = new BehaviorSubject<Record<string, ReactionInfo>>({});
|
const reactions$ = new BehaviorSubject<Record<string, ReactionInfo>>({});
|
||||||
|
|
||||||
|
const livekitRoomFactory = (): LivekitRoom =>
|
||||||
|
mockLivekitRoom({
|
||||||
|
localParticipant,
|
||||||
|
disconnect: async () => Promise.resolve(),
|
||||||
|
setE2EEEnabled: async () => Promise.resolve(),
|
||||||
|
});
|
||||||
|
|
||||||
const vm = createCallViewModel$(
|
const vm = createCallViewModel$(
|
||||||
testScope(),
|
testScope(),
|
||||||
rtcSession.asMockedSession(),
|
rtcSession.asMockedSession(),
|
||||||
@@ -181,14 +199,38 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
{
|
{
|
||||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||||
autoLeaveWhenOthersLeft: false,
|
autoLeaveWhenOthersLeft: false,
|
||||||
livekitRoomFactory: (): LivekitRoom =>
|
livekitRoomFactory,
|
||||||
mockLivekitRoom({
|
|
||||||
localParticipant,
|
|
||||||
disconnect: async () => Promise.resolve(),
|
|
||||||
setE2EEEnabled: async () => Promise.resolve(),
|
|
||||||
}),
|
|
||||||
connectionState$,
|
connectionState$,
|
||||||
windowSize$,
|
windowSize$,
|
||||||
|
localTransport: {
|
||||||
|
active$: constant({
|
||||||
|
transport: exampleTransport,
|
||||||
|
sfuConfig: exampleSfuConfig,
|
||||||
|
}),
|
||||||
|
advertised$: constant(exampleTransport),
|
||||||
|
},
|
||||||
|
connectionFactory: {
|
||||||
|
createConnection(
|
||||||
|
scope,
|
||||||
|
transport,
|
||||||
|
ownMembershipIdentity,
|
||||||
|
logger,
|
||||||
|
sfuConfig,
|
||||||
|
) {
|
||||||
|
return new MockConnection(
|
||||||
|
{
|
||||||
|
scope,
|
||||||
|
transport,
|
||||||
|
ownMembershipIdentity,
|
||||||
|
existingSFUConfig: sfuConfig,
|
||||||
|
client: room.client,
|
||||||
|
roomId: room.roomId,
|
||||||
|
livekitRoomFactory,
|
||||||
|
},
|
||||||
|
logger,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
matrixRTCMode$: constant(mode),
|
matrixRTCMode$: constant(mode),
|
||||||
...options,
|
...options,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ import {
|
|||||||
createRemoteScreenShare,
|
createRemoteScreenShare,
|
||||||
type RemoteScreenShareViewModel,
|
type RemoteScreenShareViewModel,
|
||||||
} from "../state/media/RemoteScreenShareViewModel";
|
} from "../state/media/RemoteScreenShareViewModel";
|
||||||
|
import { Connection } from "../state/CallViewModel/remoteMembers/Connection";
|
||||||
|
import { type SFUConfig } from "../livekit/openIDSFU";
|
||||||
|
|
||||||
export function withFakeTimers(continuation: () => void): void {
|
export function withFakeTimers(continuation: () => void): void {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
@@ -210,6 +212,13 @@ export const exampleTransport: LivekitTransport = {
|
|||||||
livekit_alias: "!alias:example.org",
|
livekit_alias: "!alias:example.org",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const exampleSfuConfig: SFUConfig = {
|
||||||
|
jwt: "foo",
|
||||||
|
livekitAlias: "bar",
|
||||||
|
livekitIdentity: "baz",
|
||||||
|
url: "bro",
|
||||||
|
};
|
||||||
|
|
||||||
export function mockRtcMembership(
|
export function mockRtcMembership(
|
||||||
user: string | RoomMember,
|
user: string | RoomMember,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
@@ -564,3 +573,8 @@ export function mockMuteStates(
|
|||||||
videoEnabled: false,
|
videoEnabled: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MockConnection extends Connection {
|
||||||
|
public async start(): Promise<void> {}
|
||||||
|
public async stop(): Promise<void> {}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user