Platform docs
Auth, users, webhooks & more

Ringing

Ringing

To create a ringing call, follow the same steps as in the basic call flow, with the addition of the ringing and memberIds parameters in the getOrCreateCall() method.

final call = StreamVideo.instance.makeCall(callType: StreamCallType.defaultType(), id: 'Your-call-ID');
await call.getOrCreate(memberIds: ['user1_id', 'user2_id'], ringing: true, video: true);
  • Setting ringing to true prompts Stream to send a notification to the call members, triggering the platform's call screen on iOS and Android.
  • The notification specifies whether the call is a video call or audio-only, based on the video parameter (true for video, false for audio-only).
  • memberIds is a list of user IDs to be added to the call. When combined with the ringing parameter, it triggers ringing on the devices of these members.

If the call already exists, the method will just get it and sends the notifications.

Notifying users

In some scenarios, you may prefer to notify users about joining a call without triggering ringing. To achieve this, use the notify option:

final call = StreamVideo.instance.makeCall(callType: StreamCallType.defaultType(), id: 'Your-call-ID');
await call.getOrCreate(memberIds: ['user1_id', 'user2_id'], notify: true);

When notify is set to true, Stream sends a regular push notification to all members. This is particularly useful for use cases like livestreams or huddles.

Listening to Ringing Events

When the app is active, a WebSocket event (CoordinatorCallRingingEvent) is sent if someone rings the currently logged-in user. You can listen to this event to display an in-app call screen:

final subscription = StreamVideo.instance.events.listen((event) {
    if (event is CoordinatorCallRingingEvent) {
        print(event);
    }
});

// Remember to cancel the subscription when no longer needed.
subscription.cancel();

The CoordinatorCallRingingEvent includes a video boolean property (provided in getOrCreate() method), indicating whether the call includes video or is audio-only. You can use this information to customize the in-app call screen.

Additionally, you can listen for incomingCall events from the StreamVideo object’s state. This provides a Call object for the incoming call:

final subscription = StreamVideo.instance.state.incomingCall.listen((call) {
    // Replace with navigation flow of your choice
    Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => CallScreen(call)),
    );
});

// Remember to cancel the subscription when no longer needed.
subscription.cancel();

Ringing individual members

In some cases, you may want to ring individual members instead of the whole call, or you want to ring a member into an existing call. You can do this by using the ring method:

final call = StreamVideo.instance.makeCall(callType: StreamCallType.defaultType(), id: 'Your-call-ID');
await call.getOrCreate(memberIds: ['user1_id', 'user2_id'], ringing: true, video: true);

// note: userId needs to be a member of the call
await call.ring(userIds: ['userId'])

// to invite a new member and ring them
await call.addMembers([UserInfo(id: 'userId')]);
await call.ring(userIds: ['userId']);

// to ring everyone
await call.ring();

UI Components

By navigating to a screen containing our StreamCallContainer and passing the incoming call, an incoming call screen is displayed automatically. For outgoing calls, use StreamCallContainer with the call used to initiate ringing to show an outgoing call screen.

Find more details in our UI docs: Incoming Call and Outgoing Call.

Info:

This method does not display an incoming call screen if the app is in the background or terminated. To handle such scenarios, proper VoIP push handling is required. Additionally if VoIP push/CallKit is configured, the system displays a ringing notification alongside the in-app incoming screen when the app is in the foreground.

Auto-ending the call

By default, a call initiated with the ringing flow ends automatically when only one participant remains. To disable this behavior, set the dropIfAloneInRingingFlow flag to false in CallPreferences:

final call = streamVideo.makeCall(
    callType: StreamCallType.defaultType(),
    id: 'CALL_ID',
    preferences: DefaultCallPreferences(dropIfAloneInRingingFlow: false),
);

Encrypted calls

An end-to-end encrypted call needs its key before it connects, and the ringing flow gives you nowhere to supply one: there is no lobby screen to attach an EncryptionManager from, and by the time your call screen builds the join is already under way.

Provide a key resolver instead, and the SDK sets encryption up for the call as it answers:

StreamVideo(
    apiKey,
    user: user,
    options: StreamVideoOptions(
        defaultCallPreferences: DefaultCallPreferences(
            encryptionKeyResolver: (request) async {
                // Answer only for the calls you mean to encrypt. The request
                // carries the call's id and nothing else.
                final bytes = await myKeyStore.keyForCall(request.callCid);
                if (bytes == null) return null;

                return CallEncryptionKey.shared(bytes: bytes, keyIndex: 0);
            },
        ),
    ),
);

Setting it on defaultCallPreferences covers every path an incoming call can arrive through. To scope it to answered calls only, pass the same preferences as acceptCallPreferences to observeCoreRingingEvents instead.

See End-to-End Encryption for how keys are distributed, what happens when the resolver returns null, and the alternative of attaching a manager yourself.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Video Flutter v1: <Your Question>