# Multicall

In this guide, you'll learn how to keep more than one call joined at the same time and pick the right audio policy for your UX. Two `StreamVideoOptions` fields, `allowMultipleActiveCalls` and `multiCallAudioPolicy`, cover two common patterns:

- **Foreground / background rooms**. The user joins several rooms and switches which one has mic and speaker focus, without leaving the others. Demonstrated by the [video_multicall sample app](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_multicall).
- **Pause-on-incoming-call**. An in-progress call (for example, a livestream) is paused while a new call (for example, a ringing 1:1) takes over, and resumes automatically when the new call ends. Demonstrated by the [video_livestream_with_call sample app](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_livestream_with_call).

Before you start, complete the base Flutter Video setup first:

- [Installation](/video/docs/flutter/installation/) for SDK dependencies and platform permissions.
- [Quickstart](/video/docs/flutter/quickstart/) for client initialization, authentication, and joining a single call.

This cookbook focuses only on the multicall-specific pieces that differ from basic SDK usage.

## Enable multiple active calls

Two `StreamVideoOptions` fields control the multicall behavior:

```dart title="/lib/main.dart"
StreamVideo(
  'YOUR_API_KEY',
  user: User.regular(userId: userId, name: userName),
  userToken: userToken,
  options: StreamVideoOptions(
    allowMultipleActiveCalls: true,
    // Defaults to MultiCallAudioPolicy.suspendExisting. Pick the policy
    // that matches your UX. See "Choosing an audio policy" below.
    multiCallAudioPolicy: MultiCallAudioPolicy.suspendExisting,
  ),
);
```

- `allowMultipleActiveCalls: true` lets the SDK keep more than one call joined at the same time. Without it, joining a new call leaves the previous one.
- `multiCallAudioPolicy` decides how the SDK should behave when a subsequent call is joined while another is already active. This is the part you tune per use case.

## Choosing an audio policy

Each `Call` owns its own native peer-connection factory on iOS, Android, and macOS, and each factory has its own audio device module (ADM). When two calls coexist, both ADMs would otherwise compete for the device's mic and speaker. The policy controls when the SDK calls `suspendAudio` / `resumeAudio` on a call automatically:

| Policy                      | On `setActiveCall(newCall)`                                        | On `removeActiveCall(call)`                             | Best fit                                                                                      |
| --------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `suspendExisting` (default) | Every other active call gets `suspendAudio()`                      | Most-recently-added remaining call gets `resumeAudio()` | Pause-on-incoming-call. Newest call wins, previous call resumes automatically when it leaves. |
| `suspendIncoming`           | The new call gets `suspendAudio()` (if any call is already active) | No automatic resume                                     | Foreground / background rooms. First-joined call keeps focus, later joins stay muted.         |
| `manual`                    | Nothing                                                            | Nothing                                                 | You own `Call.suspendAudio()` / `Call.resumeAudio()` entirely.                                |

:::caution
With `manual`, two unsuspended calls will contend for mic and speaker resources, which usually shows up as poor audio quality or missing audio. Only use it when you have a clear reason to manage suspension yourself.
:::

`Call.suspendAudio()` / `Call.resumeAudio()` are still available regardless of policy. The policy only controls what the SDK does **automatically** on join and leave. You can still call them yourself to drive transitions the policy doesn't model. The most common case is _switching_ between two already-joined calls (see [Use case 1](#use-case-1-foreground--background-rooms) below).

## Use case 1: Foreground / background rooms

> Full sample: [video_multicall](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_multicall)

This pattern keeps two (or more) rooms joined at once. The user can switch which room is in focus without disconnecting from the others. Useful for breakout-style conferencing, support handoffs, or any UX where the user needs to monitor several calls but only publish media to one at a time.

The right policy is `suspendIncoming`:

```dart
options: StreamVideoOptions(
  allowMultipleActiveCalls: true,
  multiCallAudioPolicy: MultiCallAudioPolicy.suspendIncoming,
),
```

### Joining a second room in the background

With `suspendIncoming`, joining a second room is just `call.join()`. The SDK suspends the new call automatically because another one is already active. Its remote audio arrives muted, and the first-joined room keeps mic and speaker focus.

```dart
// Join the first room normally. It becomes the foreground call.
final firstCall = StreamVideo.instance.makeCall(callType: ..., id: ...);
await firstCall.getOrCreate();
await firstCall.join();

// Join the second room. The SDK auto-suspends it because the first room
// is already active. No pre-suspend, post-suspend, or per-track listener
// is needed on the integrator side.
final secondCall = StreamVideo.instance.makeCall(callType: ..., id: ...);
await secondCall.getOrCreate();
await secondCall.join(
  connectOptions: CallConnectOptions(
    // You typically don't want to publish local media into a background
    // room. Disable mic/camera in the connect options.
    microphone: TrackOption.disabled(),
    camera: TrackOption.disabled(),
  ),
);
```

### Switching focus between rooms

The policy doesn't cover the "swap which existing call is in focus" operation, because no call is joining or leaving. Both stay in `activeCalls`. You drive that handoff explicitly with `suspendAudio` / `resumeAudio`:

```dart
Future<void> switchFocus({
  required Call from,
  required Call to,
}) async {
  // Turn off the user's mic/camera on the room losing focus.
  await from.setMicrophoneEnabled(enabled: false);
  await from.setCameraEnabled(enabled: false);

  // Hand the audio session over.
  await from.suspendAudio();
  await to.resumeAudio();

  // Restore the user's mic/camera preferences on the now-active room.
  await to.setMicrophoneEnabled(enabled: true);
  await to.setCameraEnabled(enabled: true);
}
```

`suspendAudio` on the outgoing call releases its mic and speaker holds and disables its audio tracks. `resumeAudio` on the incoming call re-claims them and re-enables the tracks the SDK had disabled when the call was originally backgrounded.

### Leaving a room

You don't need any explicit `resumeAudio` when a room leaves. Under `suspendIncoming` the leaving call was either:

- the foreground call (no other call needs resuming), or
- a background call (the foreground call was never suspended, so there's nothing to wake up).

If you want the remaining background room to take focus after the foreground room leaves, call `resumeAudio` on it from your UI code. The policy itself stays out of the way.

## Use case 2: Pause-on-incoming-call

> Full sample: [video_livestream_with_call](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_livestream_with_call)

This pattern keeps a long-running call going (a livestream, a meeting, an audio room) but pauses its audio whenever a new call takes over. For example, when a viewer rings the livestream host and the host accepts the 1:1 call. When the secondary call ends, the long-running call resumes.

The right policy is the default `suspendExisting`:

```dart
options: StreamVideoOptions(
  allowMultipleActiveCalls: true,
  // suspendExisting is the default, so you can omit this line. Shown
  // for clarity, since this is what makes the pattern work.
  multiCallAudioPolicy: MultiCallAudioPolicy.suspendExisting,
),
```

### Accepting an incoming call while another is active

The SDK does all the audio work for you. `setActiveCall` runs at the top of `call.join()`, which under `suspendExisting` suspends every other active call. You only need to handle UX concerns (e.g., muting your published mic/camera on the long-running call so its viewers don't see you while you take the side call):

```dart
// liveStreamCall is already joined and broadcasting.

StreamVideo.instance.state.incomingCall.listen((incoming) async {
  if (incoming == null) return;

  // Optional: stop publishing local media on the long-running call while
  // the secondary call is active. The SDK handles the audio session
  // handoff. This is purely about what your viewers see.
  await liveStreamCall.setMicrophoneEnabled(enabled: false);
  await liveStreamCall.setCameraEnabled(enabled: false);

  // Accept + join the incoming call. setActiveCall runs inside join()
  // and the SDK auto-suspends the livestream call.
  await incoming.accept();
  await incoming.join();
});
```

### Hanging up the secondary call

When the secondary call leaves, `removeActiveCall` runs, and under `suspendExisting` the SDK auto-resumes the most-recently-added remaining call (the livestream in this example):

```dart
// Triggered when the 1:1 call ends.
Future<void> onSecondaryCallEnded() async {
  // The SDK has already resumed the livestream's audio session. Re-enable
  // the host's publishing tracks so the broadcast continues normally.
  await liveStreamCall.setMicrophoneEnabled(enabled: true);
  await liveStreamCall.setCameraEnabled(enabled: true);
}
```

You don't need to call `resumeAudio` yourself. The policy handles it.

## Per-call audio configuration

`StreamVideoOptions.audioConfigurationPolicy` sets the **client-wide default** that every call uses unless overridden. In a multi-call setup you sometimes need different rooms to use different audio profiles. For example, a livestream for a viewer with `ViewerAudioPolicy` and a 1:1 call between host and participant with `BroadcasterAudioPolicy` on the same client.

Pass `audioConfigurationPolicy` on `DefaultCallPreferences` when creating the `Call`, and the per-call factory will be built with that policy instead of the client default:

```dart
final livestreamRoom = streamVideo.makeCall(
  callType: StreamCallType.livestream(),
  id: 'livestream',
  preferences: DefaultCallPreferences(
    audioConfigurationPolicy: const AudioConfigurationPolicy.viewer(),
  ),
);

final callRoom = streamVideo.makeCall(
  callType: StreamCallType.defaultType(),
  id: '1-1call',
  preferences: DefaultCallPreferences(
    audioConfigurationPolicy: const AudioConfigurationPolicy.broadcaster(),
  ),
);
```

Each call's native peer-connection factory is built with its own policy at first use (when you call `Call.join()` or render the lobby preview). The policies don't conflict across the two factories. The only shared resource is the platform audio session, which the SDK auto-coordinates through the suspend / resume flow described above.

If `audioConfigurationPolicy` is omitted from `DefaultCallPreferences`, the call falls back to `StreamVideoOptions.audioConfigurationPolicy` (or `BroadcasterAudioPolicy` if that's also unset).

## Summary

- `StreamVideoOptions(allowMultipleActiveCalls: true)` enables joining more than one call.
- `multiCallAudioPolicy` picks the audio-handoff strategy. Default `suspendExisting` matches "newest call wins" UX (pause-on-incoming-call). `suspendIncoming` keeps the first-joined call in focus and auto-mutes later joins (foreground / background rooms). `manual` opts out, leaving `suspendAudio` / `resumeAudio` entirely to you.
- You only ever call `Call.suspendAudio()` / `Call.resumeAudio()` yourself to drive transitions the policy doesn't model. Most commonly when _switching_ focus between two already-joined calls.
- `DefaultCallPreferences(audioConfigurationPolicy: ...)` overrides `StreamVideoOptions.audioConfigurationPolicy` per call, so different rooms (e.g. a viewer livestream and a broadcaster 1:1) can use different audio profiles on the same client.
- Two reference implementations live in the [flutter-video-samples](https://github.com/GetStream/flutter-video-samples) repo:
  - [video_multicall](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_multicall): `suspendIncoming`, split-room UI.
  - [video_livestream_with_call](https://github.com/GetStream/flutter-video-samples/tree/main/packages/video_livestream_with_call): `suspendExisting` (default), livestream + ringing 1:1 call.


---

This page was last updated at 2026-06-05T14:24:47.461Z.

For the most recent version of this documentation, visit [https://getstream.io/video/docs/flutter/ui-cookbook/multicall/](https://getstream.io/video/docs/flutter/ui-cookbook/multicall/).