Ringing

The Call object provides options to ring and notify users about calls.

Best Practices

  • Use unique call IDs - Reusing call IDs may cause unexpected behavior
  • Include caller in members - The caller must be in the member list
  • Handle multiple ringing calls - Filter and manage concurrent ringing calls
  • Clean up on reject/cancel - Use call.leave({ reject: true }) with appropriate reason
  • Check permissions before ending - endCall() requires special permissions

Create call

Set ring: true and provide the member list. Include the caller in the member list.

const call = client.call("default", crypto.randomUUID());
await call.getOrCreate({
  ring: true,
  video: true,
  data: {
    members: [{ user_id: "myself" }, { user_id: "my friend" }],
  },
});
When using ringing calls, it is recommended to use unique call IDs, as reusing the same call ID may lead to unexpected behavior.

Call creation options

Supported options:

OptionDescriptionDefault
membersA list of members to add to this call. You can specify the role and custom data on these members-
customAny custom data you want to store-
settingsYou can overwrite certain call settings for this specific call. This overwrites the call type standard settings-
startsAtWhen the call will start. Used for calls scheduled in the future, livestreams, audio rooms etc-
teamRestrict the access to this call to a specific team-
ringIf you want the call to ring for each memberfalse
notifyIf you want the call to notify each member by sending push notification.false
videoWhen ringing, the notification will indicate whether it’s a video call or an audio-only call, depending on whether you set the video parameter to true or false-

This starts the signaling flow. The caller auto-joins when the first callee accepts. The call stops if all callees reject.

When ring is true, a push notification will be sent to the members, provided their app have the required setup. For more details around push notifications, please check this page.

Watch for incoming and outgoing calls

Use the useCalls hook with RingingCallContent component.

Important: Watch ringing calls in your app's root component. This ensures calls display regardless of current screen or if opened from a push notification.

import { SafeAreaView, StyleSheet } from "react-native";
import {
  StreamCall,
  StreamVideo,
  useCalls,
  RingingCallContent,
  StreamVideoClient,
  User,
} from "@stream-io/video-react-native-sdk";

const user: User = { id: "sara" };
const client = StreamVideoClient.getOrCreateInstance({
  apiKey: "<STREAM-API-KEY>",
  tokenProvider: () => Promise.resolve("<USER-TOKEN>"),
  user,
});

const RingingCalls = () => {
  // collect all ringing kind of calls managed by the SDK
  const calls = useCalls().filter((c) => c.ringing);

  // for simplicity, we only take the first one but
  // there could be multiple calls ringing at the same time
  const ringingCall = calls[0];
  if (!ringingCall) return null;

  return (
    <StreamCall call={ringingCall}>
      <SafeAreaView style={StyleSheet.absoluteFill}>
        <RingingCallContent />
      </SafeAreaView>
    </StreamCall>
  );
};

export const App = () => {
  return (
    <StreamVideo client={client}>
      {/* MyApp - your parent navigator or equivalent */}
      <MyApp />
      <RingingCalls />
    </StreamVideo>
  );
};

The RingingCalls component renders over the app during incoming/outgoing calls. Alternatively, use a Modal or Dialog.

Always use StreamVideoClient.getOrCreateInstance(..) instead of new StreamVideoClient(..). Reusing the client instance preserves call accept/decline states changed while the app was in the background. The getOrCreateInstance method ensures the same user reuses the existing instance.

Optional: Adding ringing sound without push notifications

Push notifications include ringing sounds automatically. Without push notifications, add sounds manually using react-native-sound-player or similar:

Install the library:

npm install react-native-sound-player
cd ios && bundle exec pod install  # For iOS only

Add your sound files:

  • Android: Place outgoing_call.mp3 and incoming_call.mp3 in android/app/src/main/res/raw/
  • iOS: Add the sound files to your Xcode project bundle
import {
  useCallStateHooks,
  CallingState,
  useCall,
  RingingCallContent,
} from "@stream-io/video-react-native-sdk";
import SoundPlayer from "react-native-sound-player";

const RingingSound = () => {
  const call = useCall();
  const isCallCreatedByMe = call?.isCreatedByMe;
  const { useCallCallingState } = useCallStateHooks();
  const callingState = useCallCallingState();

  useEffect(() => {
    if (callingState !== CallingState.RINGING) {
      return;
    }

    if (isCallCreatedByMe) {
      // play outgoing call sound
      SoundPlayer.playSoundFile("outgoing_call", "mp3");
    } else {
      // play incoming call sound
      SoundPlayer.playSoundFile("incoming_call", "mp3");
    }

    // stop the sound when the calling state changes
    return () => {
      SoundPlayer.stop();
    };
  }, [callingState, isCallCreatedByMe]);

  // renderless component
  return null;
};

// ... later add the above component anywhere below StreamCall
<StreamCall call={call}>
  <RingingSound />
  <SafeAreaView style={StyleSheet.absoluteFill}>
    <RingingCallContent />
  </SafeAreaView>
</StreamCall>;

Canceling an outgoing call

Cancel before the first callee accepts. This stops the signaling flow.

await call.leave({ reject: true, reason: "cancel" });

Note: call.leave() after joining won't stop the signaling flow.

Rejecting an incoming call

Reject an incoming call:

await call.leave({ reject: true, reason: "decline" });

Accepting a call

Accept and join:

await call.join();

Note: Multiple calls can be joined simultaneously. Leave existing calls before accepting new ones if only one active call is allowed.

Leave call

Leave a joined call:

await call.leave();

End call

Requires special permission. Terminates the call for all participants.

await call.endCall();

Notifying

Notify users about a call without ringing using the notify option:

await call.getOrCreate({ notify: true });

Sends a regular push notification to all members. Useful for livestreams or huddles.

If the call exists, use the get method:

await call.get({ notify: true });

Ringing individual members

Ring specific members instead of the entire call, or ring members into an existing call using the ring method:

const call = client.call("default", crypto.randomUUID());
await call.getOrCreate({
  ring: false,
  data: {
    members: [{ user_id: "myself" }, { user_id: "my-friend" }],
  },
});

// note: my-friend needs to be a member of the call
await call.ring({ members_ids: ["my-friend"] });

// to invite a new member and ring them
await call.updateCallMembers({
  update_members: [{ members_ids: "my-other-friend" }],
});
await call.ring({ members_ids: ["my-other-friend"] });

// to ring all members
await call.ring();

Keeping track of the ringing state

Track accepted_by, rejected_by, and missed_by states for all rung members. Useful for displaying detailed member status in your UI.

Access this data through the call's session state:

const { useCallSession } = useCallStateHooks();
const session = useCallSession();
const { accepted_by = {}, rejected_by = {}, missed_by = {} } = session ?? {};

if (accepted_by[sara.user_id]) {
  console.log("sara accepted the call");
}

if (rejected_by[john.user_id]) {
  console.log("john rejected the call");
}

if (missed_by[mary.user_id]) {
  console.log("mary missed the call");
}