Remove unsound participant casts
By tagging participant behaviors with a type (local vs. remote) we can now tell what kind of participant it will be in a completely type-safe manner.
This commit is contained in:
@@ -110,6 +110,7 @@ import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts";
|
|||||||
import { createConnectionManager$ } from "./remoteMembers/ConnectionManager.ts";
|
import { createConnectionManager$ } from "./remoteMembers/ConnectionManager.ts";
|
||||||
import {
|
import {
|
||||||
createMatrixLivekitMembers$,
|
createMatrixLivekitMembers$,
|
||||||
|
type TaggedParticipant,
|
||||||
type MatrixLivekitMember,
|
type MatrixLivekitMember,
|
||||||
} from "./remoteMembers/MatrixLivekitMembers.ts";
|
} from "./remoteMembers/MatrixLivekitMembers.ts";
|
||||||
import {
|
import {
|
||||||
@@ -504,23 +505,28 @@ export function createCallViewModel$(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const localMatrixLivekitMember$ = scope.behavior<MatrixLivekitMember | null>(
|
const localMatrixLivekitMember$ =
|
||||||
localRtcMembership$.pipe(
|
scope.behavior<MatrixLivekitMember<"local"> | null>(
|
||||||
generateItems(
|
localRtcMembership$.pipe(
|
||||||
// Generate a local member when membership is non-null
|
generateItems(
|
||||||
function* (membership) {
|
// Generate a local member when membership is non-null
|
||||||
if (membership !== null) yield { keys: ["local"], data: membership };
|
function* (membership) {
|
||||||
},
|
if (membership !== null)
|
||||||
(_scope, membership$) => ({
|
yield { keys: ["local"], data: membership };
|
||||||
membership$,
|
},
|
||||||
participant$: localMembership.participant$,
|
(_scope, membership$) => ({
|
||||||
connection$: localMembership.connection$,
|
membership$,
|
||||||
userId,
|
participant: {
|
||||||
}),
|
type: "local" as const,
|
||||||
|
value$: localMembership.participant$,
|
||||||
|
},
|
||||||
|
connection$: localMembership.connection$,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
map(([localMember]) => localMember ?? null),
|
||||||
),
|
),
|
||||||
map(([localMember]) => localMember ?? null),
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// callLifecycle
|
// callLifecycle
|
||||||
@@ -597,7 +603,7 @@ export function createCallViewModel$(
|
|||||||
const members = membersWithEpoch.value;
|
const members = membersWithEpoch.value;
|
||||||
const a$ = combineLatest(
|
const a$ = combineLatest(
|
||||||
members.map((member) =>
|
members.map((member) =>
|
||||||
combineLatest([member.connection$, member.participant$]).pipe(
|
combineLatest([member.connection$, member.participant.value$]).pipe(
|
||||||
map(([connection, participant]) => {
|
map(([connection, participant]) => {
|
||||||
// do not render audio for local participant
|
// do not render audio for local participant
|
||||||
if (!connection || !participant || participant.isLocal)
|
if (!connection || !participant || participant.isLocal)
|
||||||
@@ -675,8 +681,10 @@ export function createCallViewModel$(
|
|||||||
let localParticipantId: string | undefined = undefined;
|
let localParticipantId: string | undefined = undefined;
|
||||||
// add local member if available
|
// add local member if available
|
||||||
if (localMatrixLivekitMember) {
|
if (localMatrixLivekitMember) {
|
||||||
const { userId, participant$, connection$, membership$ } =
|
const { userId, connection$, membership$ } =
|
||||||
localMatrixLivekitMember;
|
localMatrixLivekitMember;
|
||||||
|
const participant: TaggedParticipant =
|
||||||
|
localMatrixLivekitMember.participant; // Widen the type
|
||||||
localParticipantId = `${userId}:${membership$.value.deviceId}`; // should be membership$.value.membershipID which is not optional
|
localParticipantId = `${userId}:${membership$.value.deviceId}`; // should be membership$.value.membershipID which is not optional
|
||||||
// const participantId = membership$.value.membershipID;
|
// const participantId = membership$.value.membershipID;
|
||||||
if (localParticipantId) {
|
if (localParticipantId) {
|
||||||
@@ -686,7 +694,7 @@ export function createCallViewModel$(
|
|||||||
dup,
|
dup,
|
||||||
localParticipantId,
|
localParticipantId,
|
||||||
userId,
|
userId,
|
||||||
participant$,
|
participant,
|
||||||
connection$,
|
connection$,
|
||||||
],
|
],
|
||||||
data: undefined,
|
data: undefined,
|
||||||
@@ -697,7 +705,7 @@ export function createCallViewModel$(
|
|||||||
// add remote members that are available
|
// add remote members that are available
|
||||||
for (const {
|
for (const {
|
||||||
userId,
|
userId,
|
||||||
participant$,
|
participant,
|
||||||
connection$,
|
connection$,
|
||||||
membership$,
|
membership$,
|
||||||
} of matrixLivekitMembers) {
|
} of matrixLivekitMembers) {
|
||||||
@@ -706,7 +714,7 @@ export function createCallViewModel$(
|
|||||||
// const participantId = membership$.value?.identity;
|
// const participantId = membership$.value?.identity;
|
||||||
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
for (let dup = 0; dup < 1 + duplicateTiles; dup++) {
|
||||||
yield {
|
yield {
|
||||||
keys: [dup, participantId, userId, participant$, connection$],
|
keys: [dup, participantId, userId, participant, connection$],
|
||||||
data: undefined,
|
data: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,12 +100,12 @@ test("should signal participant not yet connected to livekit", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
||||||
a: expect.toSatisfy((data: MatrixLivekitMember[]) => {
|
a: expect.toSatisfy((data: MatrixLivekitMember<"remote">[]) => {
|
||||||
expect(data.length).toEqual(1);
|
expect(data.length).toEqual(1);
|
||||||
expectObservable(data[0].membership$).toBe("a", {
|
expectObservable(data[0].membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].participant$).toBe("a", {
|
expectObservable(data[0].participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].connection$).toBe("a", {
|
expectObservable(data[0].connection$).toBe("a", {
|
||||||
@@ -180,12 +180,12 @@ test("should signal participant on a connection that is publishing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
||||||
a: expect.toSatisfy((data: MatrixLivekitMember[]) => {
|
a: expect.toSatisfy((data: MatrixLivekitMember<"remote">[]) => {
|
||||||
expect(data.length).toEqual(1);
|
expect(data.length).toEqual(1);
|
||||||
expectObservable(data[0].membership$).toBe("a", {
|
expectObservable(data[0].membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].participant$).toBe("a", {
|
expectObservable(data[0].participant.value$).toBe("a", {
|
||||||
a: expect.toSatisfy((participant) => {
|
a: expect.toSatisfy((participant) => {
|
||||||
expect(participant).toBeDefined();
|
expect(participant).toBeDefined();
|
||||||
expect(participant!.identity).toEqual(bobParticipantId);
|
expect(participant!.identity).toEqual(bobParticipantId);
|
||||||
@@ -231,12 +231,12 @@ test("should signal participant on a connection that is not publishing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe("a", {
|
||||||
a: expect.toSatisfy((data: MatrixLivekitMember[]) => {
|
a: expect.toSatisfy((data: MatrixLivekitMember<"remote">[]) => {
|
||||||
expect(data.length).toEqual(1);
|
expect(data.length).toEqual(1);
|
||||||
expectObservable(data[0].membership$).toBe("a", {
|
expectObservable(data[0].membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].participant$).toBe("a", {
|
expectObservable(data[0].participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].connection$).toBe("a", {
|
expectObservable(data[0].connection$).toBe("a", {
|
||||||
@@ -296,7 +296,7 @@ describe("Publication edge case", () => {
|
|||||||
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe(
|
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe(
|
||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
a: expect.toSatisfy((data: MatrixLivekitMember[]) => {
|
a: expect.toSatisfy((data: MatrixLivekitMember<"remote">[]) => {
|
||||||
expect(data.length).toEqual(2);
|
expect(data.length).toEqual(2);
|
||||||
expectObservable(data[0].membership$).toBe("a", {
|
expectObservable(data[0].membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
@@ -305,7 +305,7 @@ describe("Publication edge case", () => {
|
|||||||
// The real connection should be from transportA as per the membership
|
// The real connection should be from transportA as per the membership
|
||||||
a: connectionA,
|
a: connectionA,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].participant$).toBe("a", {
|
expectObservable(data[0].participant.value$).toBe("a", {
|
||||||
a: expect.toSatisfy((participant) => {
|
a: expect.toSatisfy((participant) => {
|
||||||
expect(participant).toBeDefined();
|
expect(participant).toBeDefined();
|
||||||
expect(participant!.identity).toEqual(bobParticipantId);
|
expect(participant!.identity).toEqual(bobParticipantId);
|
||||||
@@ -362,7 +362,7 @@ describe("Publication edge case", () => {
|
|||||||
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe(
|
expectObservable(matrixLivekitMember$.pipe(map((e) => e.value))).toBe(
|
||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
a: expect.toSatisfy((data: MatrixLivekitMember[]) => {
|
a: expect.toSatisfy((data: MatrixLivekitMember<"remote">[]) => {
|
||||||
expect(data.length).toEqual(2);
|
expect(data.length).toEqual(2);
|
||||||
expectObservable(data[0].membership$).toBe("a", {
|
expectObservable(data[0].membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
@@ -371,7 +371,7 @@ describe("Publication edge case", () => {
|
|||||||
// The real connection should be from transportA as per the membership
|
// The real connection should be from transportA as per the membership
|
||||||
a: connectionA,
|
a: connectionA,
|
||||||
});
|
});
|
||||||
expectObservable(data[0].participant$).toBe("a", {
|
expectObservable(data[0].participant.value$).toBe("a", {
|
||||||
// No participant as Bob is not publishing on his membership transport
|
// No participant as Bob is not publishing on his membership transport
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { type LocalParticipant, type RemoteParticipant } from "livekit-client";
|
||||||
type LocalParticipant as LocalLivekitParticipant,
|
|
||||||
type RemoteParticipant as RemoteLivekitParticipant,
|
|
||||||
} from "livekit-client";
|
|
||||||
import {
|
import {
|
||||||
type LivekitTransport,
|
type LivekitTransport,
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
@@ -24,16 +21,24 @@ import { generateItemsWithEpoch } from "../../../utils/observable";
|
|||||||
|
|
||||||
const logger = rootLogger.getChild("[MatrixLivekitMembers]");
|
const logger = rootLogger.getChild("[MatrixLivekitMembers]");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dynamic participant value with a static tag to tell what kind of
|
||||||
|
* participant it can be (local vs. remote).
|
||||||
|
*/
|
||||||
|
export type TaggedParticipant =
|
||||||
|
| { type: "local"; value$: Behavior<LocalParticipant | null> }
|
||||||
|
| { type: "remote"; value$: Behavior<RemoteParticipant | null> };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Matrix call member and their associated LiveKit participation.
|
* Represents a Matrix call member and their associated LiveKit participation.
|
||||||
* `livekitParticipant` can be undefined if the member is not yet connected to the livekit room
|
* `livekitParticipant` can be undefined if the member is not yet connected to the livekit room
|
||||||
* or if it has no livekit transport at all.
|
* or if it has no livekit transport at all.
|
||||||
*/
|
*/
|
||||||
export interface MatrixLivekitMember {
|
export interface MatrixLivekitMember<
|
||||||
|
ParticipantType extends TaggedParticipant["type"],
|
||||||
|
> {
|
||||||
membership$: Behavior<CallMembership>;
|
membership$: Behavior<CallMembership>;
|
||||||
participant$: Behavior<
|
participant: TaggedParticipant & { type: ParticipantType };
|
||||||
LocalLivekitParticipant | RemoteLivekitParticipant | null
|
|
||||||
>;
|
|
||||||
connection$: Behavior<Connection | null>;
|
connection$: Behavior<Connection | null>;
|
||||||
// participantId: string; We do not want a participantId here since it will be generated by the jwt
|
// participantId: string; We do not want a participantId here since it will be generated by the jwt
|
||||||
// TODO decide if we can also drop the userId. Its in the matrix membership anyways.
|
// TODO decide if we can also drop the userId. Its in the matrix membership anyways.
|
||||||
@@ -61,7 +66,7 @@ export function createMatrixLivekitMembers$({
|
|||||||
scope,
|
scope,
|
||||||
membershipsWithTransport$,
|
membershipsWithTransport$,
|
||||||
connectionManager,
|
connectionManager,
|
||||||
}: Props): Behavior<Epoch<MatrixLivekitMember[]>> {
|
}: Props): Behavior<Epoch<MatrixLivekitMember<"remote">[]>> {
|
||||||
/**
|
/**
|
||||||
* Stream of all the call members and their associated livekit data (if available).
|
* Stream of all the call members and their associated livekit data (if available).
|
||||||
*/
|
*/
|
||||||
@@ -110,12 +115,14 @@ export function createMatrixLivekitMembers$({
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
`Updating data$ for participantId: ${participantId}, userId: ${userId}`,
|
`Updating data$ for participantId: ${participantId}, userId: ${userId}`,
|
||||||
);
|
);
|
||||||
|
const { participant$, ...rest } = scope.splitBehavior(data$);
|
||||||
// will only get called once per `participantId, userId` pair.
|
// will only get called once per `participantId, userId` pair.
|
||||||
// updates to data$ and as a result to displayName$ and mxcAvatarUrl$ are more frequent.
|
// updates to data$ and as a result to displayName$ and mxcAvatarUrl$ are more frequent.
|
||||||
return {
|
return {
|
||||||
participantId,
|
participantId,
|
||||||
userId,
|
userId,
|
||||||
...scope.splitBehavior(data$),
|
participant: { type: "remote" as const, value$: participant$ },
|
||||||
|
...rest,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expectObservable(matrixLivekitItems$).toBe(vMarble, {
|
expectObservable(matrixLivekitItems$).toBe(vMarble, {
|
||||||
a: expect.toSatisfy((e: Epoch<MatrixLivekitMember[]>) => {
|
a: expect.toSatisfy((e: Epoch<MatrixLivekitMember<"remote">[]>) => {
|
||||||
const items = e.value;
|
const items = e.value;
|
||||||
expect(items.length).toBe(1);
|
expect(items.length).toBe(1);
|
||||||
const item = items[0]!;
|
const item = items[0]!;
|
||||||
@@ -147,12 +147,12 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
expectObservable(item.participant$).toBe("a", {
|
expectObservable(item.participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
b: expect.toSatisfy((e: Epoch<MatrixLivekitMember[]>) => {
|
b: expect.toSatisfy((e: Epoch<MatrixLivekitMember<"remote">[]>) => {
|
||||||
const items = e.value;
|
const items = e.value;
|
||||||
expect(items.length).toBe(2);
|
expect(items.length).toBe(2);
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
expectObservable(item.membership$).toBe("a", {
|
expectObservable(item.membership$).toBe("a", {
|
||||||
a: bobMembership,
|
a: bobMembership,
|
||||||
});
|
});
|
||||||
expectObservable(item.participant$).toBe("a", {
|
expectObservable(item.participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
expectObservable(item.membership$).toBe("a", {
|
expectObservable(item.membership$).toBe("a", {
|
||||||
a: carlMembership,
|
a: carlMembership,
|
||||||
});
|
});
|
||||||
expectObservable(item.participant$).toBe("a", {
|
expectObservable(item.participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
expectObservable(item.connection$).toBe("a", {
|
expectObservable(item.connection$).toBe("a", {
|
||||||
@@ -189,7 +189,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
c: expect.toSatisfy((e: Epoch<MatrixLivekitMember[]>) => {
|
c: expect.toSatisfy((e: Epoch<MatrixLivekitMember<"remote">[]>) => {
|
||||||
const items = e.value;
|
const items = e.value;
|
||||||
expect(items.length).toBe(3);
|
expect(items.length).toBe(3);
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
|||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
expectObservable(item.participant$).toBe("a", {
|
expectObservable(item.participant.value$).toBe("a", {
|
||||||
a: null,
|
a: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import type { ReactionOption } from "../reactions";
|
|||||||
import { observeSpeaker$ } from "./observeSpeaker.ts";
|
import { observeSpeaker$ } from "./observeSpeaker.ts";
|
||||||
import { generateItems } from "../utils/observable.ts";
|
import { generateItems } from "../utils/observable.ts";
|
||||||
import { ScreenShare } from "./ScreenShare.ts";
|
import { ScreenShare } from "./ScreenShare.ts";
|
||||||
|
import { type TaggedParticipant } from "./CallViewModel/remoteMembers/MatrixLivekitMembers.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorting bins defining the order in which media tiles appear in the layout.
|
* Sorting bins defining the order in which media tiles appear in the layout.
|
||||||
@@ -68,40 +69,46 @@ enum SortingBin {
|
|||||||
* for inclusion in the call layout and tracks associated screen shares.
|
* for inclusion in the call layout and tracks associated screen shares.
|
||||||
*/
|
*/
|
||||||
export class UserMedia {
|
export class UserMedia {
|
||||||
public readonly vm: UserMediaViewModel = this.participant$.value?.isLocal
|
public readonly vm: UserMediaViewModel =
|
||||||
? new LocalUserMediaViewModel(
|
this.participant.type === "local"
|
||||||
this.scope,
|
? new LocalUserMediaViewModel(
|
||||||
this.id,
|
this.scope,
|
||||||
this.userId,
|
this.id,
|
||||||
this.participant$ as Behavior<LocalParticipant | null>,
|
this.userId,
|
||||||
this.encryptionSystem,
|
this.participant.value$,
|
||||||
this.livekitRoom$,
|
this.encryptionSystem,
|
||||||
this.focusUrl$,
|
this.livekitRoom$,
|
||||||
this.mediaDevices,
|
this.focusUrl$,
|
||||||
this.displayName$,
|
this.mediaDevices,
|
||||||
this.mxcAvatarUrl$,
|
this.displayName$,
|
||||||
this.scope.behavior(this.handRaised$),
|
this.mxcAvatarUrl$,
|
||||||
this.scope.behavior(this.reaction$),
|
this.scope.behavior(this.handRaised$),
|
||||||
)
|
this.scope.behavior(this.reaction$),
|
||||||
: new RemoteUserMediaViewModel(
|
)
|
||||||
this.scope,
|
: new RemoteUserMediaViewModel(
|
||||||
this.id,
|
this.scope,
|
||||||
this.userId,
|
this.id,
|
||||||
this.participant$ as Behavior<RemoteParticipant | null>,
|
this.userId,
|
||||||
this.encryptionSystem,
|
this.participant.value$,
|
||||||
this.livekitRoom$,
|
this.encryptionSystem,
|
||||||
this.focusUrl$,
|
this.livekitRoom$,
|
||||||
this.pretendToBeDisconnected$,
|
this.focusUrl$,
|
||||||
this.displayName$,
|
this.pretendToBeDisconnected$,
|
||||||
this.mxcAvatarUrl$,
|
this.displayName$,
|
||||||
this.scope.behavior(this.handRaised$),
|
this.mxcAvatarUrl$,
|
||||||
this.scope.behavior(this.reaction$),
|
this.scope.behavior(this.handRaised$),
|
||||||
);
|
this.scope.behavior(this.reaction$),
|
||||||
|
);
|
||||||
|
|
||||||
private readonly speaker$ = this.scope.behavior(
|
private readonly speaker$ = this.scope.behavior(
|
||||||
observeSpeaker$(this.vm.speaking$),
|
observeSpeaker$(this.vm.speaking$),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TypeScript needs this widening of the type to happen in a separate statement
|
||||||
|
private readonly participant$: Behavior<
|
||||||
|
LocalParticipant | RemoteParticipant | null
|
||||||
|
> = this.participant.value$;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All screen share media associated with this user media.
|
* All screen share media associated with this user media.
|
||||||
*/
|
*/
|
||||||
@@ -184,9 +191,7 @@ export class UserMedia {
|
|||||||
private readonly scope: ObservableScope,
|
private readonly scope: ObservableScope,
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
private readonly userId: string,
|
private readonly userId: string,
|
||||||
private readonly participant$: Behavior<
|
private readonly participant: TaggedParticipant,
|
||||||
LocalParticipant | RemoteParticipant | null
|
|
||||||
>,
|
|
||||||
private readonly encryptionSystem: EncryptionSystem,
|
private readonly encryptionSystem: EncryptionSystem,
|
||||||
private readonly livekitRoom$: Behavior<LivekitRoom | undefined>,
|
private readonly livekitRoom$: Behavior<LivekitRoom | undefined>,
|
||||||
private readonly focusUrl$: Behavior<string | undefined>,
|
private readonly focusUrl$: Behavior<string | undefined>,
|
||||||
|
|||||||
Reference in New Issue
Block a user