Skip to main content

Calls

Creating calls

You can create a call by providing the call type and an ID:

  • The call type controls which features are enabled and sets up permissions. Call type settings and permissions can be set from API, or using the Stream Dashboard.
  • Calls can be used once or multiple times depending on your app. Unless you want to re-use the same call multiple times, the recommended way to pick a call ID is to use a uuid v4 so that each call gets a unique random ID.

You can specify call members who can receive push notification about the call.

It's also possible to store any custom data with the call object.

const callType = 'default';
const callId = 'my-first-call';
const call = client.video.call(callType, callId);

call.create({ data: { created_by_id: 'john' } });

// optionally provide additional data
call.create({
data: {
created_by_id: 'john',
// Call members need to be existing users
members: [{ user_id: 'john', role: 'admin' }, { user_id: 'jack' }],
custom: {
color: 'blue',
},
},
});

// Upsert behavior
call.getOrCreate({data: /* */});

ring flag

To start the ringing flow, we need to set the ring flag to true and provide the list of members we want to call. It is important to note that the caller should also be included in the list of members.

// or call.create
// or call.get
await client.call('default', 'test-outgoing-call').getOrCreate({
ring: true,
data: {
created_by_id: 'myself',
members: [{ user_id: 'myself' }, { user_id: 'my friend' }],
},
});

This step will start the signaling flow and send the call.ring WebSocket event. It's also possible to send push notifications to call members on this event, for more information see the Call Types page.

Call members can decide to accept or reject the incoming call. The callee can decide to cancel the outgoing call (for more information see the SDK documentations).

notify flag

Setting the notify flag to true will send the call.notification WebSocket event. It's also possible to send push notifications to call members on this event, for more information see the Call Types page.

// or call.create
// or call.get
await client.call('default', 'test-outgoing-call').getOrCreate({
notify: true,
data: {
created_by_id: 'myself',
members: [{ user_id: 'myself' }, { user_id: 'my friend' }],
},
});

Model

GetOrCreateCallRequest

NameTypeDescriptionConstraints
dataCallRequest--
members_limitinteger-Maximum: 100
notifybooleanif provided it sends a notification event to the members for this call-
ringbooleanif provided it sends a ring event to the members for this call-
videoboolean--

CallRequest

NameTypeDescriptionConstraints
created_byUserRequest--
created_by_idstring--
customobject--
membersMemberRequest[]-Maximum: 100
settings_overrideCallSettingsRequest--
starts_atnumber--
teamstring--
videoboolean--

UserRequest

NameTypeDescriptionConstraints
customobject--
idstringUser IDRequired
imagestring--
invisibleboolean--
languagestring--
namestringOptional name of user-
privacy_settingsPrivacySettings--
push_notificationsPushNotificationSettingsInput--
rolestring--
teamsstring[]--

MemberRequest

NameTypeDescriptionConstraints
customobjectCustom data for this object-
rolestring--
user_idstring-Required

CallSettingsRequest

NameTypeDescriptionConstraints
audioAudioSettingsRequest--
backstageBackstageSettingsRequest--
broadcastingBroadcastSettingsRequest--
geofencingGeofenceSettingsRequest--
limitsLimitsSettingsRequest--
recordingRecordSettingsRequest--
ringRingSettingsRequest--
screensharingScreensharingSettingsRequest--
thumbnailsThumbnailsSettingsRequest--
transcriptionTranscriptionSettingsRequest--
videoVideoSettingsRequest--

PrivacySettings

NameTypeDescriptionConstraints
read_receiptsReadReceipts--
typing_indicatorsTypingIndicators--

PushNotificationSettingsInput

NameTypeDescriptionConstraints
disabledNullBool--
disabled_untilNullTime--

AudioSettingsRequest

NameTypeDescriptionConstraints
access_request_enabledboolean--
default_devicestring (speaker, earpiece)-Required
mic_default_onboolean--
noise_cancellationNoiseCancellationSettings--
opus_dtx_enabledboolean--
redundant_coding_enabledboolean--
speaker_default_onboolean--

BackstageSettingsRequest

NameTypeDescriptionConstraints
enabledboolean--
join_ahead_time_secondsinteger--

BroadcastSettingsRequest

NameTypeDescriptionConstraints
enabledboolean--
hlsHLSSettingsRequest--
rtmpRTMPSettingsRequest--

GeofenceSettingsRequest

NameTypeDescriptionConstraints
namesstring[]--

LimitsSettingsRequest

NameTypeDescriptionConstraints
max_duration_secondsinteger-Minimum: 0
max_participantsinteger--

RecordSettingsRequest

NameTypeDescriptionConstraints
audio_onlyboolean--
layoutLayoutSettingsRequest--
modestring (available, disabled, auto-on)-Required
qualitystring (360p, 480p, 720p, 1080p, 1440p, portrait-360x640, portrait-480x854, portrait-720x1280, portrait-1080x1920, portrait-1440x2560)--

RingSettingsRequest

NameTypeDescriptionConstraints
auto_cancel_timeout_msinteger-Required, Minimum: 5000, Maximum: 180000
incoming_call_timeout_msinteger-Required, Minimum: 5000, Maximum: 180000
missed_call_timeout_msinteger-Minimum: 5000, Maximum: 180000

ScreensharingSettingsRequest

NameTypeDescriptionConstraints
access_request_enabledboolean--
enabledboolean--
target_resolutionTargetResolution--

ThumbnailsSettingsRequest

NameTypeDescriptionConstraints
enabledboolean--

TranscriptionSettingsRequest

NameTypeDescriptionConstraints
closed_caption_modestring--
languagesstring[]-Maximum: 2
modestring (available, disabled, auto-on)-Required

VideoSettingsRequest

NameTypeDescriptionConstraints
access_request_enabledboolean--
camera_default_onboolean--
camera_facingstring (front, back, external)--
enabledboolean--
target_resolutionTargetResolution--

ReadReceipts

NameTypeDescriptionConstraints
enabledboolean--

TypingIndicators

NameTypeDescriptionConstraints
enabledboolean--

NullBool

NameTypeDescriptionConstraints
HasValueboolean--
Valueboolean--

NullTime

NameTypeDescriptionConstraints
HasValueboolean--
Valuenumber--

NoiseCancellationSettings

NameTypeDescriptionConstraints
modestring (available, disabled, auto-on)-Required

HLSSettingsRequest

NameTypeDescriptionConstraints
auto_onboolean--
enabledboolean--
layoutLayoutSettingsRequest--
quality_tracksstring[]-Required, Minimum: 1, Maximum: 3

RTMPSettingsRequest

NameTypeDescriptionConstraints
enabledboolean--
layoutLayoutSettingsRequestLayout for the composed RTMP stream-
qualitystring (360p, 480p, 720p, 1080p, 1440p, portrait-360x640, portrait-480x854, portrait-720x1280, portrait-1080x1920, portrait-1440x2560)Resolution to set for the RTMP stream-

LayoutSettingsRequest

NameTypeDescriptionConstraints
external_app_urlstring--
external_css_urlstring--
namestring (spotlight, grid, single-participant, mobile, custom)-Required
optionsobject--

TargetResolution

NameTypeDescriptionConstraints
bitrateinteger-Required, Maximum: 6000000
heightinteger-Required, Minimum: 240, Maximum: 3840
widthinteger-Required, Minimum: 240, Maximum: 3840

Call types

Call types provide sensible default settings for different use-cases. We provide the following types out of the box:

  • Default (default) for 1:1 or group calls that use both video and audio
  • Livestreaming (livestream) to build ultra low latency livestreaming for your app on our global edge network. Broadcast from your phone or RTMP and scale to millions of participants.
  • Audio room (audio_room) to build audio experiences for your app. You can build basic calling or feature rich experience like Twitter spaces. Audio quality, reliability and scalability is far ahead of competing solutions.
  • Development (development) This call type comes with almost all permission settings disabled so that it is simpler to get your initial implementation up and running. You should only use this call type early-on during development.

Each of our SDKs have tutorials specific for each call type. If you want to know the default settings for each of the call types check out the Built-in call types page.

It's possible to tweak the built-in call types or create new ones.

Updating calls

Default call settings are inherited from the call type. These settings can be overridden if necessary.

call.update({
settings_override: {
audio: { mic_default_on: true, default_device: 'speaker' },
},
});

// or to update custom data
call.update({ custom: { color: 'red' } });

Manage call members

You can provide a list of call members, this can be done when you create a call or later on when the call already exists. Please note that call members need to be existing users.

There are two reasons to use call members:

  • Call membership allows you to have more flexibility when it comes to permissions. The permission system allows you to grant different permissions to users and members, this way one user can be a member on one call or a member on another. Membership also allows you to grant additional roles to users in a call. It's important to note that this doesn't restrict access, but rather expands capabilities. You can more information about the roles and permissions here.
  • Call members will receive push notifications.
// Call members need to be existing users
call.updateCallMembers({
// You can add new members
// You can also update the role of existing members
update_members: [{ user_id: 'sara' }, { user_id: 'emily', role: 'admin' }],
});

You can also remove call members:

call.updateCallMembers({
remove_members: ['sara'],
});

Restrict call access

You can restrict access to a call by updating the Call Type permissions and roles. A typical use case is to restrict access to a call to a specific set of users -> call members.

By default, all users unless specified otherwise, have the user role.

You can remove the join-call permission from the user role for the given call type scope. This will prevent regular users from joining a call of the given type.

Next, let's ensure that the call_member role has the join-call permission for the given call type scope. This will allow users with the call_member role to join a call of this type.

Once this is set, we can proceed with setting up a call instance and providing the members.

const callTypeName = 'default';

const callType = (await client.video.listCallTypes()).call_types[callTypeName];
// Remove JOIN_CALL permission from user role
const userGrants = callType.grants['user'].filter(
(c) => c !== OwnCapability.JOIN_CALL,
);
// Make sure JOIN_CALL permission is set for call_member role
const callMemberGrants = callType.grants['call_member'];
if (!callMemberGrants.includes(OwnCapability.JOIN_CALL)) {
callMemberGrants.push(OwnCapability.JOIN_CALL);
}

// Update the call type with the changes
await client.video.updateCallType({
name: callTypeName,
grants: {
user: userGrants,
call_member: callMemberGrants,
},
});

Sessions

Call IDs can be reused, which means a call can be joined and left by participants multiple times. Every time the first participant joins the call, a new session is started. Every time the last participant left the call, the session is ended.

  • If a call session is started, the call.session_started WebSocket event will be dispatched. It's also possible to send push notifications to call members on this event, for more information see the Call Types page.

  • If a call session is ended, the call.session_ended WebSocket event will be dispatched.

Ending calls

This action terminates the call for everyone. Ending a call requires the end-call permission.

await call.end();

Only users with the join-ended-call permission can join an ended call.

Query calls

For many video calling, live stream, or audio rooms apps, you'll want to show:

  • Upcoming calls
  • Calls that are currently live
  • Popular live streams / audio rooms with a link to the recording

Examples

Below you can find a few examples of different queries:

Sorting and pagination

// default sorting
client.video.queryCalls();

// sorting and pagination
const queryCallsReq = {
sort: [{ field: 'starts_at', direction: -1 }],
limit: 2,
};
response = await client.video.queryCalls(queryCallsReq);

// loading next page
client.video.queryCalls({
...queryCallsReq,
next: response.next,
});

Query live calls

client.video.queryCalls({
filter_conditions: { backstage: { $eq: false } },
});

Query upcoming calls

const mins30 = 1000 * 60 * 30;
const inNext30mins = new Date(Date.now() + mins30);
client.video.queryCalls({
filter_conditions: {
starts_at: { $gt: inNext30mins.toISOString() },
},
});

Query ongoing calls

client.video.queryCalls({
filter_conditions: { ongoing: { $eq: true } },
});

Sort options

Sorting is supported on these fields:

  • starts_at
  • created_at
  • updated_at
  • ended_at
  • type
  • id
  • cid

Filter options

NameTypeDescriptionSupported operators
idstringCall ID$in, $eq, $gt, $gte, $lt, $lte, $exists
cidstringCall CID (format: type:id)$in, $eq, $gt, $gte, $lt, $lte, $exists
teamstringThe team associated with the channel$in, $eq, $gt, $gte, $lt, $lte, $exists
typestringCall type$in, $eq, $gt, $gte, $lt, $lte, $exists
created_by_user_idstringUser ID of the call's creator$in, $eq, $gt, $gte, $lt, $lte, $exists
created_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)Creation time of the call$in, $eq, $gt, $gte, $lt, $lte, $exists
updated_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)The time of the last update of the call$in, $eq, $gt, $gte, $lt, $lte, $exists
ended_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)The time the call was ended$in, $eq, $gt, $gte, $lt, $lte, $exists
starts_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)The scheduled start time of the call$in, $eq, $gt, $gte, $lt, $lte, $exists
backstagebooleantrue if the call is in backstage mode$eq
membersstring arrayMember user ids$in
ongoingbooleantrue if there is an ongoing session for the call$eq

The Stream API allows you to specify filters and ordering for several endpoints. The query syntax is similar to that of Mongoose, however we do not run MongoDB on the backend. Only a subset of the MongoDB operations are supported.

NameDescriptionExample
$eqMatches values that are equal to a specified value.{ "key": { "$eq": "value" } } or the simplest form { "key": "value" }
$qFull text search (matches values where the whole text value matches the specified value){ "key": { "$q": "value } }
$gtMatches values that are greater than a specified value.{ "key": { "$gt": 4 } }
$gteMatches values that are greater than or equal to a specified value.{ "key": { "$gte": 4 } }
$ltMatches values that are less than a specified value.{ "key": { "$lt": 4 } }
$lteMatches values that are less than or equal to a specified value.{ "key": { "$lte": 4 } }
$inMatches any of the values specified in an array.{ "key": { "$in": [ 1, 2, 4 ] } }
$existsMathces values that either have (when set to true) or not have (when set to false) certain attributes{ "key": { "$exists": true } }
$autocompleteMathces values that start with the specified string value{ "key": { "$autocomplete": "value" } }

It's also possible to combine filter expressions with the following operators:

NameDescriptionExample
$andMatches all the values specified in an array.{ "$and": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "some_other_key": 10 } ] }
$orMatches at least one of the values specified in an array.{ "$or": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "key2": 10 } ] }

Query call members

// default sorting
call.queryMembers();

// sorting and pagination
const queryMembersReq = {
sort: [{ field: 'user_id', direction: 1 }],
limit: 2,
};
const response = await call.queryMembers({ payload: queryMembersReq });

// loading next page
call.queryMembers({
payload: {
...queryMembersReq,
next: response.next,
},
});

// filtering
call.queryMembers({
payload: { filter_conditions: { role: { $eq: 'admin' } } },
});

Sort options

Sorting is supported on these fields:

  • user_id
  • created_at

Filter options

NameTypeDescriptionSupported operators
user_idstringUser ID$in, $eq, $gt, $gte, $lt, $lte, $exists
rolestringThe role of the user$in, $eq, $gt, $gte, $lt, $lte, $exists
customObjectSearch in custom membership data, example syntax: {'custom.color': {$eq: 'red'}}$in, $eq, $gt, $gte, $lt, $lte, $exists
created_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)Creation time of the user$in, $eq, $gt, $gte, $lt, $lte, $exists
updated_atstring, must be formatted as an RFC3339 timestamp (for example 2021-01-15T09:30:20.45Z)The time of the last update of the user$in, $eq, $gt, $gte, $lt, $lte, $exists

The Stream API allows you to specify filters and ordering for several endpoints. The query syntax is similar to that of Mongoose, however we do not run MongoDB on the backend. Only a subset of the MongoDB operations are supported.

NameDescriptionExample
$eqMatches values that are equal to a specified value.{ "key": { "$eq": "value" } } or the simplest form { "key": "value" }
$qFull text search (matches values where the whole text value matches the specified value){ "key": { "$q": "value } }
$gtMatches values that are greater than a specified value.{ "key": { "$gt": 4 } }
$gteMatches values that are greater than or equal to a specified value.{ "key": { "$gte": 4 } }
$ltMatches values that are less than a specified value.{ "key": { "$lt": 4 } }
$lteMatches values that are less than or equal to a specified value.{ "key": { "$lte": 4 } }
$inMatches any of the values specified in an array.{ "key": { "$in": [ 1, 2, 4 ] } }
$existsMathces values that either have (when set to true) or not have (when set to false) certain attributes{ "key": { "$exists": true } }
$autocompleteMathces values that start with the specified string value{ "key": { "$autocomplete": "value" } }

It's also possible to combine filter expressions with the following operators:

NameDescriptionExample
$andMatches all the values specified in an array.{ "$and": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "some_other_key": 10 } ] }
$orMatches at least one of the values specified in an array.{ "$or": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "key2": 10 } ] }

Send custom event

It's possible to send any custom event for a call:

// send a custom event to all users watching the call
call.sendCallEvent({
custom: {
'render-animation': 'balloons',
},
user_id: 'john',
});

Sending a custom event will dispatch the custom WebSocket event.

Pin and unpin video

You can pin the video of a participant in a call session. The SDKs will make sure that the pinned participant is displayed in a prominent location in the call layout for all participants. You can also unpin a pinned participant if you no longer want to highlight them.

await call.videoPin({
session_id: 'session-id',
user_id: 'user-id-to-pin',
});

await call.videoUnpin({
session_id: 'session-id',
user_id: 'user-id-to-unpin',
});

Session Timers

You can limit the maximum duration for calls. This limit can be set for individual calls or set to a default value for all calls with the same type. When set, users will be automatically removed from a call when the time runs out and the call will be marked as ended.

Please note that by default, most call types are configured so that users can join ended calls. To ensure session timers work correctly, the JoinEndedCall permission should be disabled at the call level. This can be done from the dashboard.

Call type configuration

You can configure all calls to have a default max duration, this can be done from the Dashboard (Call Type Settings) or using the API.

await client.video.updateCallType({
name: 'default',
settings: {
limits: {
max_duration_seconds: 3600,
},
},
});

// Disable the default session timer
await client.video.updateCallType({
name: 'default',
settings: {
limits: {
max_duration_seconds: 0,
},
},
});

Create call with session timer

It is possible to create calls with a different max duration than the default defined at the call type.

// or call.create
await client.call('default', 'test-outgoing-call').getOrCreate({
data: {
created_by_id: 'john',
settings_override: {
limits: {
max_duration_seconds: 3600,
},
},
},
});

Updating call object to extend session time

It is possible to update a call and extend the session time. In that case a call.updated event is sent to all connected clients so that their UI can be updated accordingly.

// Update the call with session timer
await client.call.update({
data: {
settings_override: {
limits: {
max_duration_seconds: call.settings.limits.max_duration_seconds + 300,
},
},
},
});

// Disable the session timer
await client.call.update({
data: {
settings_override: {
limits: {
max_duration_seconds: 0,
},
},
},
});

Did you find this page helpful?