Lobby Preview

In this article, we will discuss key considerations for creating an inviting and user-friendly entrance to your application. We will delve into the various elements that can enhance the user experience and ensure a smooth transition from the lobby to the video call itself. By implementing effective lobby page design principles, you can elevate the overall user satisfaction. We consider lobby to be place, where the user acquires information about the call to be joined and can configure own media devices.

The approach to visualise the components will differ from application to application. Therefore, in this guide, we will focus only on the principles of building components and plugging them with right data sources. We will not focus on the styling (CSS) part.

Prerequisites

Before we can join a call, we need to connect to Stream’s edge infrastructure. To do that, we follow these steps:

  • Register for a Stream account and obtain our API key and secret.
  • Install the Stream React Video SDK:
  • npm install @stream-io/video-react-sdk
  • yarn add @stream-io/video-react-sdk
  • Initialize the SDK by passing in your API key, token and user information:

For the token generation, you can use our Token Generator.

The call data

We would like to show some basic information about the call, when users arrive to the lobby. For example:

  • call name
  • who is invited
  • who already joined

The initial call information can be retrieved by the get or getOrCreate method of a Call instance. Then we can use the following hooks:

  • useCallSession
  • useCallMembers

These hooks make sure, that the information about call session or call members is updated in real time. The updates are made automatically in response to Stream’s WebSocket events arriving from the backend.

Learn more about setting up the boilerplate of joining a call room in our Joining and Creating Calls guide.

Video input preview

The SDK comes with a pre-build VideoPreview component that handles video input stream preview. It also presents various UIs based on video playing state (starting, playing, unavailable video devices). In the example below, we are assembling a custom video preview using SDK’s VideoPreview component and our custom UI components for each playing state.

import {
  useConnectedUser,
  DefaultVideoPlaceholder,
  StreamVideoParticipant,
  VideoPreview,
} from "@stream-io/video-react-sdk";

import { CameraIcon, LoadingIndicator } from "../icons";

export const Lobby = () => {
  return (
    <div>
      {/* ... other components */}
      <VideoPreview
        DisabledVideoPreview={DisabledVideoPreview}
        NoCameraPreview={NoCameraPreview}
        StartingCameraPreview={StartingCameraPreview}
      />
      {/* ... other components */}
    </div>
  );
};

export const DisabledVideoPreview = () => {
  const connectedUser = useConnectedUser();
  if (!connectedUser) return null;

  return (
    <DefaultVideoPlaceholder
      participant={
        {
          image: connectedUser.image,
          name: connectedUser.name,
        } as StreamVideoParticipant
      }
    />
  );
};

const NoCameraPreview = () => (
  <div>
    <CameraIcon />
  </div>
);

const StartingCameraPreview = () => (
  <div>
    <LoadingIndicator />
  </div>
);

Audio input preview

Microphone configuration in the lobby may consist of checking, whether our microphone works and deciding, whether it will be enabled, when we join the call.

Learn how to build a toggle button for call preview pages in Call controls tutorial.

We build our custom sound detector in the dedicated tutorial about Audio Volume Indicator.

Device selection

Switching devices is done through a set of utility methods or hooks. We speak a bit more about the function and the pre-built components in the media devices guide.

Device selector example

The selectors can be thought of as dropdowns in our example.

import { useCallStateHooks } from "@stream-io/video-react-sdk";

type DeviceSelectorProps = {
  devices: MediaDeviceInfo[];
  selectedDeviceId?: string;
  onSelect: (deviceId: string) => void;
};

export const DeviceSelector = ({
  devices,
  selectedDeviceId,
  onSelect,
}: DeviceSelectorProps) => {
  return (
    <div className="selector">
      {devices.map((device) => {
        const selected = selectedDeviceId === device.deviceId;
        return (
          <div
            key={device.deviceId}
            className={`option ${selected ? "option--selected" : ""}`}
            onClick={() => onSelect(device.deviceId)}
          >
            {device.label}
          </div>
        );
      })}
    </div>
  );
};

export const AudioInputDeviceSelector = () => {
  const { useMicrophoneState } = useCallStateHooks();
  const { microphone, devices, selectedDevice } = useMicrophoneState();
  return (
    <DeviceSelector
      devices={devices || []}
      selectedDeviceId={selectedDevice}
      onSelect={(deviceId) => microphone.select(deviceId)}
    />
  );
};

export const VideoInputDeviceSelector = () => {
  const { useCameraState } = useCallStateHooks();
  const { camera, devices, selectedDevice } = useCameraState();
  return (
    <DeviceSelector
      devices={devices || []}
      selectedDeviceId={selectedDevice}
      onSelect={(deviceId) => camera.select(deviceId)}
    />
  );
};

export const AudioOutputDeviceSelector = () => {
  const { useSpeakerState } = useCallStateHooks();
  const { speaker, devices, selectedDevice, isDeviceSelectionSupported } =
    useSpeakerState();

  if (!isDeviceSelectionSupported) return null;
  return (
    <DeviceSelector
      devices={devices || []}
      selectedDeviceId={selectedDevice}
      onSelect={(deviceId) => speaker.select(deviceId)}
    />
  );
};

Participants in a call

We can retrieve the list of members, that already joined the call (participants), by inspecting the call session object (session.participants). The object is provided and maintained up-to-date by useCallSession hook.

import { Avatar, useCallStateHooks } from "@stream-io/video-react-sdk";

export const ParticipantsPreview = () => {
  const { useCallSession } = useCallStateHooks();
  const session = useCallSession();

  if (session?.participants || session?.participants.length === 0) return null;

  return (
    <div>
      <div>Already in call ({session.participants.length}):</div>
      <div style={{ display: "flex" }}>
        {session.participants.map((participant) => (
          <div>
            <Avatar
              name={participant.user.name}
              imageSrc={participant.user.image}
            />
            {participant.user.name && <div>{participant.user.name}</div>}
          </div>
        ))}
      </div>
    </div>
  );
};

Joining the call button

Lastly, to join a call we simply invoke call.join(). Learn more about the topic in the dedicated Joining & Creating Calls guide.

© Getstream.io, Inc. All Rights Reserved.