mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-10 05:57:07 +00:00
Compute the 'waiting for media' state less implicitly
On second glance, the way that we determined a media tile to be 'waiting for media' was too implicit for my taste. It would appear on a surface reading to depend on whether a participant was currently publishing any video. But in reality, the 'video' object was always defined as long as a LiveKit participant existed, so in reality it depended on just the participant. We should show this relationship more explicitly by moving the computation into the view model, where it can depend on the participant directly.
This commit is contained in:
@@ -12,7 +12,11 @@ import { axe } from "vitest-axe";
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
import { GridTile } from "./GridTile";
|
||||
import { mockRtcMembership, createRemoteMedia } from "../utils/test";
|
||||
import {
|
||||
mockRtcMembership,
|
||||
createRemoteMedia,
|
||||
mockRemoteParticipant,
|
||||
} from "../utils/test";
|
||||
import { GridTileViewModel } from "../state/TileViewModel";
|
||||
import { ReactionsSenderProvider } from "../reactions/useReactionsSender";
|
||||
import type { CallViewModel } from "../state/CallViewModel/CallViewModel";
|
||||
@@ -31,11 +35,11 @@ test("GridTile is accessible", async () => {
|
||||
rawDisplayName: "Alice",
|
||||
getMxcAvatarUrl: () => "mxc://adfsg",
|
||||
},
|
||||
{
|
||||
mockRemoteParticipant({
|
||||
setVolume() {},
|
||||
getTrackPublication: () =>
|
||||
({}) as Partial<RemoteTrackPublication> as RemoteTrackPublication,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const fakeRtcSession = {
|
||||
|
||||
@@ -69,6 +69,7 @@ interface UserMediaTileProps extends TileProps {
|
||||
vm: UserMediaViewModel;
|
||||
mirror: boolean;
|
||||
locallyMuted: boolean;
|
||||
waitingForMedia?: boolean;
|
||||
primaryButton?: ReactNode;
|
||||
menuStart?: ReactNode;
|
||||
menuEnd?: ReactNode;
|
||||
@@ -79,6 +80,7 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
vm,
|
||||
showSpeakingIndicators,
|
||||
locallyMuted,
|
||||
waitingForMedia,
|
||||
primaryButton,
|
||||
menuStart,
|
||||
menuEnd,
|
||||
@@ -194,7 +196,7 @@ const UserMediaTile: FC<UserMediaTileProps> = ({
|
||||
raisedHandTime={handRaised ?? undefined}
|
||||
currentReaction={reaction ?? undefined}
|
||||
raisedHandOnClick={raisedHandOnClick}
|
||||
localParticipant={vm.local}
|
||||
waitingForMedia={waitingForMedia}
|
||||
focusUrl={focusUrl}
|
||||
audioStreamStats={audioStreamStats}
|
||||
videoStreamStats={videoStreamStats}
|
||||
@@ -290,6 +292,7 @@ const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const waitingForMedia = useBehavior(vm.waitingForMedia$);
|
||||
const locallyMuted = useBehavior(vm.locallyMuted$);
|
||||
const localVolume = useBehavior(vm.localVolume$);
|
||||
const onSelectMute = useCallback(
|
||||
@@ -311,6 +314,7 @@ const RemoteUserMediaTile: FC<RemoteUserMediaTileProps> = ({
|
||||
<UserMediaTile
|
||||
ref={ref}
|
||||
vm={vm}
|
||||
waitingForMedia={waitingForMedia}
|
||||
locallyMuted={locallyMuted}
|
||||
mirror={false}
|
||||
menuStart={
|
||||
|
||||
@@ -47,7 +47,6 @@ describe("MediaView", () => {
|
||||
video: trackReference,
|
||||
userId: "@alice:example.com",
|
||||
mxcAvatarUrl: undefined,
|
||||
localParticipant: false,
|
||||
focusable: true,
|
||||
};
|
||||
|
||||
@@ -66,24 +65,13 @@ describe("MediaView", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("with no participant", () => {
|
||||
it("shows avatar for local user", () => {
|
||||
render(
|
||||
<MediaView {...baseProps} video={undefined} localParticipant={true} />,
|
||||
);
|
||||
describe("with no video", () => {
|
||||
it("shows avatar", () => {
|
||||
render(<MediaView {...baseProps} video={undefined} />);
|
||||
expect(
|
||||
screen.getByRole("img", { name: "@alice:example.com" }),
|
||||
).toBeVisible();
|
||||
expect(screen.queryAllByText("Waiting for media...").length).toBe(0);
|
||||
});
|
||||
it("shows avatar and label for remote user", () => {
|
||||
render(
|
||||
<MediaView {...baseProps} video={undefined} localParticipant={false} />,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("img", { name: "@alice:example.com" }),
|
||||
).toBeVisible();
|
||||
expect(screen.getByText("Waiting for media...")).toBeVisible();
|
||||
expect(screen.queryByTestId("video")).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,6 +82,22 @@ describe("MediaView", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("waitingForMedia", () => {
|
||||
test("defaults to false", () => {
|
||||
render(<MediaView {...baseProps} />);
|
||||
expect(screen.queryAllByText("Waiting for media...").length).toBe(0);
|
||||
});
|
||||
test("shows and is accessible", async () => {
|
||||
const { container } = render(
|
||||
<TooltipProvider>
|
||||
<MediaView {...baseProps} waitingForMedia={true} />
|
||||
</TooltipProvider>,
|
||||
);
|
||||
expect(await axe(container)).toHaveNoViolations();
|
||||
expect(screen.getByText("Waiting for media...")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
describe("unencryptedWarning", () => {
|
||||
test("is shown and accessible", async () => {
|
||||
const { container } = render(
|
||||
|
||||
@@ -43,7 +43,7 @@ interface Props extends ComponentProps<typeof animated.div> {
|
||||
raisedHandTime?: Date;
|
||||
currentReaction?: ReactionOption;
|
||||
raisedHandOnClick?: () => void;
|
||||
localParticipant: boolean;
|
||||
waitingForMedia?: boolean;
|
||||
audioStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||
videoStreamStats?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||
// The focus url, mainly for debugging purposes
|
||||
@@ -71,7 +71,7 @@ export const MediaView: FC<Props> = ({
|
||||
raisedHandTime,
|
||||
currentReaction,
|
||||
raisedHandOnClick,
|
||||
localParticipant,
|
||||
waitingForMedia,
|
||||
audioStreamStats,
|
||||
videoStreamStats,
|
||||
focusUrl,
|
||||
@@ -129,7 +129,7 @@ export const MediaView: FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!video && !localParticipant && (
|
||||
{waitingForMedia && (
|
||||
<div className={styles.status}>
|
||||
{t("video_tile.waiting_for_media")}
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
mockRtcMembership,
|
||||
createLocalMedia,
|
||||
createRemoteMedia,
|
||||
mockRemoteParticipant,
|
||||
} from "../utils/test";
|
||||
import { SpotlightTileViewModel } from "../state/TileViewModel";
|
||||
import { constant } from "../state/Behavior";
|
||||
@@ -33,7 +34,7 @@ test("SpotlightTile is accessible", async () => {
|
||||
rawDisplayName: "Alice",
|
||||
getMxcAvatarUrl: () => "mxc://adfsg",
|
||||
},
|
||||
{},
|
||||
mockRemoteParticipant({}),
|
||||
);
|
||||
|
||||
const vm2 = createLocalMedia(
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
type MediaViewModel,
|
||||
ScreenShareViewModel,
|
||||
type UserMediaViewModel,
|
||||
type RemoteUserMediaViewModel,
|
||||
} from "../state/MediaViewModel";
|
||||
import { useInitial } from "../useInitial";
|
||||
import { useMergedRefs } from "../useMergedRefs";
|
||||
@@ -84,6 +85,21 @@ const SpotlightLocalUserMediaItem: FC<SpotlightLocalUserMediaItemProps> = ({
|
||||
|
||||
SpotlightLocalUserMediaItem.displayName = "SpotlightLocalUserMediaItem";
|
||||
|
||||
interface SpotlightRemoteUserMediaItemProps
|
||||
extends SpotlightUserMediaItemBaseProps {
|
||||
vm: RemoteUserMediaViewModel;
|
||||
}
|
||||
|
||||
const SpotlightRemoteUserMediaItem: FC<SpotlightRemoteUserMediaItemProps> = ({
|
||||
vm,
|
||||
...props
|
||||
}) => {
|
||||
const waitingForMedia = useBehavior(vm.waitingForMedia$);
|
||||
return (
|
||||
<MediaView waitingForMedia={waitingForMedia} mirror={false} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
interface SpotlightUserMediaItemProps extends SpotlightItemBaseProps {
|
||||
vm: UserMediaViewModel;
|
||||
}
|
||||
@@ -103,7 +119,7 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
||||
return vm instanceof LocalUserMediaViewModel ? (
|
||||
<SpotlightLocalUserMediaItem vm={vm} {...baseProps} />
|
||||
) : (
|
||||
<MediaView mirror={false} {...baseProps} />
|
||||
<SpotlightRemoteUserMediaItem vm={vm} {...baseProps} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user