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 IDs can be reused, which means they can be joined multiple times, so it's possible to set up recurring calls.
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.
- JavaScript
- Python
- cURL
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: /* */});
import uuid
from getstream.models.call_request import CallRequest
call = client.video.call("default", uuid.uuid4())
call.create(
data=CallRequest(
created_by_id="sacha",
),
)
# optionally provide additional data
call.create(
data=CallRequest(
created_by_id="sacha",
# note: you can add users as members to calls to support more complex permissions
members=[
MemberRequest(user_id="john", role="admin"),
MemberRequest(user_id="jack"),
],
custom={"color": "blue"},
),
)
curl -X POST "https://video.stream-io-api.com/video/call/${CALL_TYPE}/${CALL_ID}?api_key=${API_KEY}" \
-H "Authorization: ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-H "stream-auth-type: jwt" \
-d '{
"data": {
"created_by_id": "sacha@getstream.io",
"members": [
{ "role": "speaker", "user_id": "sacha@getstream.io" }
],
"custom": { "color": "blue" }
}
}'
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.
- JavaScript
// 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.
- JavaScript
// 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' }],
},
});
Updating calls
Default call settings are inherited from the call type. These settings can be overridden if necessary.
- JavaScript
- Python
- cURL
call.update({
settings_override: {
audio: { mic_default_on: true, default_device: 'speaker' },
},
});
// or to update custom data
call.update({ custom: { color: 'red' } });
from getstream.models import CallSettingsRequest
# update some custom data for this call
call.update(custom={'color': 'red'})
# update settings for this call
call.update(
settings_override=CallSettingsRequest(
screensharing=ScreensharingSettingsRequest(
enabled=True, access_request_enabled=True
),
),
)
curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}?api_key=${API_KEY}" \
-H "Authorization: ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-H "stream-auth-type: jwt" \
-d '{
"settings_override": {
"audio": {
"mic_default_on": true
}
}
}'
Manage call members
You can provide a list of call members. Call members need to be existing users. Every call member has a call-level role, you can configure roles on the call type.
Call members can receive push notifications.
- JavaScript
- Python
- cURL
// 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' }],
});
# Call members need to be existing users
# You can also update the role of existing members
call.update_call_members(
update_members=[
MemberRequest(user_id="sara"),
MemberRequest(user_id="emily", role="admin"),
]
)
curl -X PUT "https://video.stream-io-api.com/video/call/default/${CALL_ID}/members?api_key=${API_KEY}" \
-H "Authorization: ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-H "stream-auth-type: jwt" \
-d '{
"update_members": [
{ "user_id": "sara" },
{ "user_id": "emily", "role": "admin" }
]
}'
You can also remove call members:
- JavaScript
- Python
call.updateCallMembers({
remove_members: ['sara'],
});
call.update_call_members(remove_members=["jack", "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
.
- JavaScript
const callType = (await client.video.listCallTypes()).call_types[callTypeName];
// Remove JOIN_CALL permission from user role
const userGrants = callType.grants['user'].filter(
(c) => c !== VideoOwnCapability.JOIN_CALL,
);
// Make sure JOIN_CALL permission is set for call_member role
const callMemberGrants = callType.grants['call_member'];
if (!callMemberGrants.includes(VideoOwnCapability.JOIN_CALL)) {
callMemberGrants.push(VideoOwnCapability.JOIN_CALL);
}
// Update the call type with the changes
await client.video.updateCallType(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.
- JavaScript
await call.endCall();
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
- JavaScript
- Python
// 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,
});
# default sorting
client.query_calls()
# sorting and pagination
response = client.query_calls(
sort= [SortParamRequest(field: 'starts_at', direction: -1)],
limit=2,
)
# loading next page
client.query_calls(
sort= [SortParamRequest(field: 'starts_at', direction: -1)],
limit=2,
next=response.data().next
)
Query live calls
- JavaScript
- Python
client.video.queryCalls({
filter_conditions: { backstage: { $eq: false } },
});
client.video.query_calls(
filter_conditions={'backstage': {'$eq': False}}
)
Query upcoming calls
- JavaScript
- Python
const mins30 = 1000 * 60 * 60 * 30;
const inNext30mins = new Date(Date.now() + mins30);
client.video.queryCalls({
filter_conditions: {
starts_at: { $gt: inNext30mins.toISOString() },
},
});
from datetime import datetime, timedelta
min30s = timedelta(minutes=30)
in_next_30_mins = datetime.now() + min30s
client.video.query_calls(
filter_conditions={'starts_at': {'$gt': in_next_30_mins.isoformat()}}
)
Query ongoing calls
- JavaScript
- Python
client.video.queryCalls({
filter_conditions: { ongoing: { $eq: true } },
});
client.video.query_calls(
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
Name | Type | Description | Supported operators |
---|---|---|---|
id | string | Call ID | $in , $eq , $gt, $gte , $lt , $lte , $exists |
cid | string | Call CID (format: type:id) | $in , $eq , $gt, $gte , $lt , $lte , $exists |
team | string | The team associated with the channel | $in , $eq , $gt, $gte , $lt , $lte , $exists |
type | string | Call type | $in , $eq , $gt, $gte , $lt , $lte , $exists |
created_by_user_id | string | User ID of the call's creator | $in , $eq , $gt, $gte , $lt , $lte , $exists |
created_at | string, 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_at | string, 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_at | string, 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_at | string, 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 |
backstage | boolean | true if the call is in backstage mode | $eq |
members | string array | Member user ids | $in |
ongoing | boolean | true 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.
Name | Description | Example |
---|---|---|
$eq | Matches values that are equal to a specified value. | { "key": { "$eq": "value" } } or the simplest form { "key": "value" } |
$q | Full text search (matches values where the whole text value matches the specified value) | { "key": { "$q": "value } } |
$gt | Matches values that are greater than a specified value. | { "key": { "$gt": 4 } } |
$gte | Matches values that are greater than or equal to a specified value. | { "key": { "$gte": 4 } } |
$lt | Matches values that are less than a specified value. | { "key": { "$lt": 4 } } |
$lte | Matches values that are less than or equal to a specified value. | { "key": { "$lte": 4 } } |
$in | Matches any of the values specified in an array. | { "key": { "$in": [ 1, 2, 4 ] } } |
$exists | Mathces values that either have (when set to true ) or not have (when set to false ) certain attributes | { "key": { "$exists": true } } |
$autocomplete | Mathces values that start with the specified string value | { "key": { "$autocomplete": "value" } } |
It's also possible to combine filter expressions with the following operators:
Name | Description | Example |
---|---|---|
$and | Matches all the values specified in an array. | { "$and": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "some_other_key": 10 } ] } |
$or | Matches at least one of the values specified in an array. | { "$or": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "key2": 10 } ] } |
Query call members
- JavaScript
- Python
// default sorting
call.queryMembers();
// sorting and pagination
const queryMembersReq = {
sort: [{ field: 'user_id', direction: 1 }],
limit: 2,
};
const response = await call.queryMembers(queryMembersReq);
// loading next page
call.queryMembers({
...queryMembersReq,
next: response.next,
});
// filtering
call.queryMembers({
filter_conditions: { role: { $eq: 'admin' } },
});
# default sorting
call.query_members()
# sorting and pagination
response = call.query_members(
sort: [SortParamRequest(field: "user_id", direction: 1)],
limit: 2,
)
# loading next page
call.query_members(
sort: [SortParamRequest(field: "user_id", direction: 1)],
limit: 2,
next: response.next,
)
# filtering
call.query_members(
filter_conditions: {"role": {"$eq": "admin"}},
)
Sort options
Sorting is supported on these fields:
user_id
created_at
Filter options
Name | Type | Description | Supported operators |
---|---|---|---|
user_id | string | User ID | $in , $eq , $gt, $gte , $lt , $lte , $exists |
role | string | The role of the user | $in , $eq , $gt, $gte , $lt , $lte , $exists |
custom | Object | Search in custom user data | $in , $eq , $gt, $gte , $lt , $lte , $exists |
created_at | string, 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_at | string, 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.
Name | Description | Example |
---|---|---|
$eq | Matches values that are equal to a specified value. | { "key": { "$eq": "value" } } or the simplest form { "key": "value" } |
$q | Full text search (matches values where the whole text value matches the specified value) | { "key": { "$q": "value } } |
$gt | Matches values that are greater than a specified value. | { "key": { "$gt": 4 } } |
$gte | Matches values that are greater than or equal to a specified value. | { "key": { "$gte": 4 } } |
$lt | Matches values that are less than a specified value. | { "key": { "$lt": 4 } } |
$lte | Matches values that are less than or equal to a specified value. | { "key": { "$lte": 4 } } |
$in | Matches any of the values specified in an array. | { "key": { "$in": [ 1, 2, 4 ] } } |
$exists | Mathces values that either have (when set to true ) or not have (when set to false ) certain attributes | { "key": { "$exists": true } } |
$autocomplete | Mathces values that start with the specified string value | { "key": { "$autocomplete": "value" } } |
It's also possible to combine filter expressions with the following operators:
Name | Description | Example |
---|---|---|
$and | Matches all the values specified in an array. | { "$and": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "some_other_key": 10 } ] } |
$or | Matches 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:
- JavaScript
// the custom event can be any kind of data
await call.sendCustomEvent({
type: 'draw',
x: 10,
y: 30,
});
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.
- JavaScript
await call.pinVideo({
session_id: 'session-id',
user_id: 'user-id-to-unpin',
});
await call.unpinVideo({
session_id: 'session-id',
user_id: 'user-id-to-pin',
});