By default clients connecting to a Room subscribe to all tracks published and as such all participants can see and hear all other participants. The Track Subscription API allows you to change this behavior and manage a Room's communication topology via the REST API. There are many possibilities; for example one participant could be set up as a silent observer, or the Room could be arranged so that all participants are only subscribed to the video and audio of a single presenter. Specifically, the API allows you to:
Check the SDK Compatibility section below for compatibility information.
You can also enable and disable participants' tracks on the client side with clientTrackSwitchOffControl
when connecting to the Room. Learn more about this option in Understanding the Network Bandwidth Profile API.
The Track Subscription API is a REST API. However, it fires subscription events that must be managed client-side, which is only available in the following SDK versions:
Twilio SDK | Track Subscriptions Support |
---|---|
JavaScript | SDK 1.15.0+ |
iOS | SDK 2.4.0+ |
Android | SDK 3.0.0+ |
For further information on how to manage client side subscription events and errors check the Managing Track Subscriptions with Twilio Client SDKs section below.
Twilio Rooms media communications work though a publish/subscribe model that relies on the following principles:
This document describes how the Track Subscription API can be used for setting which participants subscribe to which tracks. Some possible subscription models developers can implement using this API are the following:
By default, Rooms use a "subscribe-to-all" model. Hence, each participant subscribes to all the tracks published to the room by the rest of participants.
For the sake of simplicity, we define the Participant Resource URI as:
PARTICIPANT_URI = https://video.twilio.com/v1/Rooms/{RoomNameOrSid}/Participants/{ParticipantIdentityOrSid}
As children, the resources of the Track Subscription API are:
PARTICIPANT_URI/SubscribedTracks
GET
: Lists the Tracks subscribed by the specified Participant.PARTICIPANT_URI/SubscribedTracks/{TrackSid}/
GET
: Retrieves the SubscribedTrack instance resource with information about the
subscription of the specified Track by the specified Participant.PARTICIPANT_URI/SubscribeRules
GET
: Retrieves the subscribe rules for the specified Participant.POST
: Updates the subscribe rules of the specified ParticipantA SubscribedTrack
instance is a Participant
's resource that represents the
subscription such Participant has for a given Track.
PARTICIPANT_URI/SubscribedTracks/{TrackSid}/
This resource has the following properties:
The unique string that we created to identify the RoomParticipantSubscribedTrack resource.
^MT[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
The SID of the participant that subscribes to the track.
^PA[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
The SID of the participant that publishes the track.
^PA[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
The SID of the room where the track is published.
^RM[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
The track name. Must have no more than 128 characters and be unique among the participant's published tracks.
The date and time in GMT when the resource was created specified in ISO 8601 format.
The date and time in GMT when the resource was last updated specified in ISO 8601 format.
The track type. Can be: audio
, video
or data
.
audio
video
data
The absolute URL of the resource.
Retrieves the SubscribedTrack
instance resource associated to the specified Participant and TrackSid.
This resource does not support POST
, PUT
or DELETE
. For modifying the
track subscriptions of a participant see the SubscribeRules Resource
section below.
The SubscribedTracks
list resource represents the subscriptions of a
given Participant in a Room.
PARTICIPANT_URI/SubscribedTracks
Retrieves the list of subscribed tracks associated to the specified Participant
with paging data.
This resource does not support POST
, PUT
or DELETE
. For modifying the
track subscriptions of a participant see the SubscribeRules Resource
section below.
The SubscribeRules
resource is a singleton resource that represents the
subscribe rules that are enforced on a given Participant.
PARTICIPANT_URI/SubscribeRules
This resource has the following properties:
The SID of the Participant resource for the Subscribe Rules.
^PA[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
The SID of the Room resource for the Subscribe Rules
^RM[0-9a-fA-F]{32}$
Min length: 34
Max length: 34
A collection of Subscribe Rules that describe how to include or exclude matching tracks. See the Specifying Subscribe Rules section for further information.
The date and time in GMT when the resource was created specified in ISO 8601 format.
Returns the subscribe rules of the specified Participant.
Updates the subscribe rules of the specified Participant. The following parameters are supported:
application/x-www-form-urlencoded
A JSON-encoded array of subscribe rules. See the Specifying Subscribe Rules section for further information.
For a deeper understanding on how subscribe rules work, see the Understanding Subscribe Rules section below.
This resource does not support PUT
or DELETE
.
Subscribe rules can be set:
POST
) for further information.A subscription rule instance is a JSON with the following structure:
{"type": rule_type, filter_name: filter_value, filter_name: filter_value, ...}
Where:
Field | Description |
---|---|
rule_type | An identifier specifying the type of rule. Can be one of the following:-include : includes the tracks that match the filters into the subscription.-exclude : excludes the tracks that match the filters from the subscription. |
filter_name and filter_value | The filter_name must be one of the following:-all : the filter affects all tracks. Accepts only one value: true (matches all tracks)-kind : matches tracks of a given type. Accepts video , audio and data as values.-publisher : matches all tracks published by the Participant with the identity (case sensitive) or SID specified as value-track : matches tracks with the name (case sensitive) or SID specified as value. A rule containing multiple filters matches the set of tracks that comply with all of them. In other words, filters combine in a rule through a logical AND. Note also that the tracks published by a participant are never considered when evaluating such participant's filters. As a consequence, a participant cannot subscribe to her own tracks. |
Based on this, subscribe rules are specified as a JSON array containing up to 20 rules. For example:
1Rules = [2{"type": "include", "all": true}, //rule_13{"type": "exclude", "kind": "video"}, //rule_24{"type": "include", "publisher": "Bob", "track": "screen"}, //rule_35{"type": "include", "track": "MTXXXXXXXXXXXXXXXXXXXXXXXXXXX"}, //rule_46...7rule_208]
Remember that valid subscription rules comply with the following:
[]
) is not allowed."all"
filter must have a value of true
. Notice that
this means false
is not allowed.“all”
filter cannot have any other filters."type"
field."kind"
filter is used, its value must be one of audio
, video
or data
.kind
s or publisher
s, etc.)When invalid rules are specified, the currently active rules will not be updated
and the POST
will be answered with an HTTP 400
error response like the following:
1{2"code": 53215,3"message": "Invalid Subscribe Rule(s)",4"more_info": "https://www.twilio.com/docs/errors/53215",5"status": 4006}
For example, the following requests are invalid:
1//Invalid because it's using an empty set2Rules = []
1//Invalid because it uses false as value of "all"2Rules = [3{"type":"include", "all": false}4]
1//Invalid because it specifies a non supported kind2Rules = [3{"type": "include", "all": true},4{"type": "exclude", "kind": "wideo"}5]
1//Invalid because it repeats the "kind" filter twice2Rules = [3{"type": "include", "kind": "audio", "kind": "data"},4]
1//Invalid because an "all" filter is not compatible with any other filter2Rules = [3{"type": "include", "all": "true", "kind": "data"},4]
SubscribeRules
are enforced by Twilio using four main principles:
We define the Set of Subscribed Tracks (SetST) as the set of tracks a given participant should be subscribed to at any time. The SetST is computed by Twilio using Algebra of Sets based on the following algorithm:
include
rule we perform the union of the SetST with the set of
tracks matching the rule filters.exclude
rule we perform the set difference of the SetST with
the set of tracks matching by the rule filters.Let's illustrate this using an example. Imagine a Room with three participants named: Alice, Bob and Carl, who publish the tacks specified in the following table:
Alice (PTA) | Bob (PTB) | Carl (PTC) | |
---|---|---|---|
Audio Track | MTA_A alice-audio | MTB_A bob-audio | MTC_A carl-audio |
Video Track (cam) | MTA_C alice-cam | MTB_C bob-cam | MTC_C carl-cam |
Video Track (screen) | MTA_S screen | MTB_S screen | |
Data Track | MTC_D carl-data |
Notice that, for the sake of simplicity, we have assumed the following conventions:
MT
followed by the participant initial and by a
letter identifying the track nature (_A
for audio, _C
for webcam, _S
for
screenshare and _D
for data).In this context, imagine that we POST
the following rules to Alice's
/SubscribeRules
resource:
1Rules = [2{"type": "include", "all": true},3{"type": "exclude", "kind": "video"},4{"type": "include", "publisher": "Bob", "track": "screen"}5]
Then, the algorithm will compute the tracks Alice should subscribe to in the following way:
Alice's rules (applied in the specified order) | Tracks in the SetST |
---|---|
SetST is initialized to the empty set. | [] |
{"type": "include", "all": true} Includes all tracks (except Alice's ones.) | [MTB_A,MTB_C,MTB_S,MTC_A,MTC_C,MTC_D] |
{"type": "exclude", "kind": "video"} Excludes all video tracks. | [MTB_A,MTC_A,MTC_D] |
{"type": "include", "publisher": "Bob", "track": "screen"} Includes Bob's tracks named screen . | [MTB_A,MTC_A,MTC_D,MTB_S] Alice is finally subscribed to these Tracks. |
Still assuming the above specified Room scenario, consider the following examples that might be interesting to understand how the subscription algorithm works:
When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "include", "all": true}3]
Alice will subscribe to all the tracks (except for her own). Hence, the SetST will be:
[MTB_A,MTB_C,MTB_S,MTC_A,MTC_C,MTC_D]
This happens because:
[]
When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "exclude", "all": true}3]
Alice will subscribe to no tracks:
[] //The empty set
This happens because:
[]
When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "exclude", "kind": "video"}3]
Alice's subscribed tracks will be:
[] //The empty set
This happens because:
[]
.When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "include", "track": "alice-audio"}3]
Alice's subscribed tracks will be:
[] //The empty set
This happens because:
[]
.When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "include", "track": "screen"}3]
Alice's subscribed tracks will be:
[MTB_S] //Bob's screen track
This happens because:
[]
.screen
. There are two of them, one
owned by Alice. As filters do not consider the participant's own tracks, then
Alice subscribes to the union of [MTB_S]
and the empty set. Filters containing
participant or track names are case sensitive.When POST
ing to Carl's subscribe rules the following:
1Rules = [2{"type": "include", "all": true},3{"type": "exclude", "publisher": "PTB", "kind": "audio"},4{"type": "exclude", "kind": "video", "track": "screen"}5]
Carl's subscribed tracks will be:
[MTA_A, MTA_C, MTB_C]
This happens because:
[]
.[MTA_A,MTA_C,MTA_S,MTB_A,MTB_C,MTB_S]
PTB
is Bob's SID). That is, MTB_A
is excluded.screen
.
Hence, MTA_S
and MTB_S
are also excluded.When POST
ing to Alice's subscribe rules the following:
1Rules = [2{"type": "include", "kind": "data"},3{"type": "exclude", "publisher": "Carl"}4]
Alice's subscribed tracks will be:
[] //The empty set
This happens because:
[]
.MTC_D
MTC_D
and
results in the empty set.However, when POST
ing
1Rules = [2{"type": "exclude", "publisher": "Carl"},3{"type": "include", "kind": "data"}4]
Alice's subscribed tracks will be:
[MTC_D]
This happens because:
[]
.MTC_D
to the SetST.The Track Subscription API is stateless. This means that it has no memory of
rules or subscribed tracks. Every time a developer POST
s a set of new subscription
rules, the previous rules are fully erased and replaced with new rules, which
are then enforced using the algorithm described in the section above.
To illustrate this, and assuming the Room scenario described above, let's
imagine that Carl wants to subscribe to all audio tracks and exclude the rest.
POST
ing the following rules will do it:
1Rules = [2{"type": "include", "kind": "audio"}3]
Imagine now that Carl, while keeping his current audio subscriptions, also
wants to subscribe to Alice's screenshare. Carl could do it POST
ing the following:
1Rules = [2{"type": "include", "kind": "audio"},3{"type": "include", "track": "MTA_S"}4]
Observe that if the first rule is omitted, given the stateless nature of the
Track Subscription API, Carl would be subscribed only to MTA_S
but not to
Alice's and Bob's audios.
If now Carl wants to stop receiving Alice's screen, but instead wants to see
Bob's webcam while still receiving all the audios, the POST
should contain
something like this:
1Rules = [2{"type": "include", "kind": "audio"},3{"type": "include", "track": "MTB_C"}4]
As you can see, each POST
"resets" the subscribe rules making it
necessary to specify what should be included and excluded at all requests.
When developers POST
subscribe rules to Twilio, those rules are enforced in
a dynamic way. That means that the algorithm does not only execute at POST
time,
but it is executed every time there is change in the Room's available tracks.
Hence, once the rules have been POST
ed, Twilio guarantees that they are enforced
at any time without requiring further developer intervention.
To illustrate this, let's imagine that the sequence of actions in our example room is the following:
In this scenario, imagine that the following subscribe rules are POST
ed
to Alice's /SubscribedTracks
just after she joins:
1Rules=[2{"type": "include", "kind": "audio"},3{"type": "include", "track": "screen"},4{"type": "exclude", "publisher": "Carl"},5{"type": "include", "kind": "data"}6]
Just after the POST
, Alice's subscribed tracks are:
[] //The empty set
This happens because Alice is alone in the room and cannot subscribe to her own published tracks, no matter what the subscribe rules are.
Following the sequence above, now Bob connects to the Room publishing his 3 tracks. At that moment, given the declarative nature of subscribe rules, Alice's subscribed tracks are re-evaluated to be:
[MTB_A, MTB_S]
Observe the above rules avoid using SIDs in the filters, we can create rules affecting Alice's subscriptions to Bob's tracks even before Bob connects to the room.
Next Carl joins and publishes audio and video tracks. Given that the third rule excludes Carl's tracks, Alice's subscribed tracks stay the same:
[MTB_A, MTB_S]
After that, Carl publishes his Data Track. As the fourth rule is including all
tracks with kind equal to data
, Alice's subscribed tracks evolve to:
[MTB_A, MTB_S, MTC_D]
At the fifth step, Bob leaves the room. Alice's subscriptions will be modified to be:
[MTC_D]
Last, when Carl leaves, Alice's will no be subscribed to any track any longer.
[] //The empty set
When a participant joins a Room, Twilio automatically feeds a subscription
rule to that participant. We call it the default_rule
:
{"type": "include", "all": true}
This happens automatically and only once at the time the participant connects. Twilio does this for enforcing a default "subscribe-to-all" model. This means that, if no further rules are provided, participants will connect to all the published tracks, which is consistent with Rooms default behavior prior to the existence of the Track Subscription API.
Thanks to this, Twilio guarantees backwards compatibility of all Room
applications. However, this has a drawback. To understand
why, let's use an example. Imagine that a developer wants Carl to just publish
to the room but not to subscribe to anything. In that case, the developer may
POST
the following to Carl's rules:
Rules=[{"type":"exclude", "all": true}]
Indeed, this will remove the default rule with a new one enforcing a
"subscribe-to-none" model. However this POST
can only be sent after Carl
connects. Hence, there is a race condition between the default rule, which
tries to subscribe Carl to all the tracks, and the POST
that tries to remove all
subscriptions. This may degrade Carl's experience with some annoying
subscriptions that appear and disappear.
To avoid this, Twilio client SDK APIs allow to override the default rule
at connect
time. Section Overriding defaults below is devoted to
explain how.
Twilio Video SDKs allows overriding the "subscribe-to-all" default_rule
at connect time and replace it with a "subscribe-to-none" one:
{"type": "exclude", "all": true}
This is achieved through the automaticSubscription
parameter as the
following code snippets illustrate.
JavaScript SDK (v2.0.0-beta12+ required)
1const { connect } = require('twilio-video');2connect(token, {3automaticSubscription: false,4name: 'my-room'5});6
iOS SDK (v.2.10.0+ required)
1let connectOptions = TVIConnectOptions(token: accessToken) { (builder) in2builder.isAutomaticSubscriptionEnabled = false3builder.roomName = "my-room"4}5self.room = TwilioVideo.connect(with: connectOptions, delegate: self)
Android SDK (v.4.1.0+ required)
Java
1ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)2.enableAutomaticSubscription(false)3.roomName("my-room")4.build();56room = Video.connect(context, connectOptions, roomListener);
Kotlin
1val connectOptions = ConnectOptions.Builder(accessToken)2.enableAutomaticSubscription(false)3.roomName("my-room")4.build()56room = Video.connect(context, connectOptions, roomListener)
Every time subscriptions change Twilio sends events to the corresponding participant SDK. Developers must manage such events as part of their client application logic. There are three types of events:
Event type | Event description |
---|---|
subscribed | Fired when the Participant starts receiving media from a newly subscribed Track. Handling this event typically consists on rendering the received media (e.g. attaching to a video tag, etc.) |
unsubscribed | Fired when the Participant unsubscribes from a previously subscribed Track. Handling this event typically consists on adapting the GUI to stop rendering the track media (e.g. detaching from a video tag, etc.) |
error | Fired when the Participant should have subscribed to a Track but could not do it. This happens, for example, when the Participant does not support codecs of Tracks included by her subscribe rules. Handling this event typically requires informing the end-user and, eventually, taking the appropriate actions for minimizing the error impact. |
The following code snippet illustrates how to manage subscribe events in Twilio Video JavaScript SDK:
12const { connect } = require('twilio-video');34function subscribed(track) {5console.log('Subscribed to RemoteTrack:', track.sid);6//Code for starting track rendering goes here.7}89function unsubscribed(track) {10console.log('Unsubscribed to RemoteTrack:', track.sid);11//Code for stopping track rendering goes here.12}1314function subscriptionFailed(error, publication) {15console.log('Failed to subscribe to RemoteTrack ${publication.trackSid}:', error);16//Code for managing subscribe errors goes here.17}1819function listenToSubscriptionEvents(publication) {20publication.on('subscribed', subscribed);21publication.on('unsubscribed', unsubscribed);22publication.on('subscriptionFailed', subscriptionFailed);23}2425function participantConnected(participant) {26// Listen to subscription events of already published Tracks.27participant.tracks.forEach(listenToSubscriptionEvents);28// Listen to subscription events of Tracks that are published later.29room.on('trackPublished', listenToSubscriptionEvents);30}3132connect('$token', { name: 'my-room' }).then(room => {33// Listen to events from RemoteParticipants currently in the Room.34room.participants.forEach(participantConnected);35// Listen to events from RemoteParticipants that will join the Room.36room.on('participantConnected', participantConnected);37});
The following code snippet illustrates how to manage subscribe events in Twilio Video iOS SDK. For the sake of simplicity, we have only considered subscription event from video tracks. Handlers for audio and data tracks are equivalent but using their corresponding data types.
1let options = TVIConnectOptions(token: accessToken)2room = TwilioVideo.connect(with: options, delegate: self)34func didConnect(to room: TVIRoom) {5// Handle subscription events for connected Participants6for remoteParticipant in room.remoteParticipants {7remoteParticipant.delegate = self8}9}1011func room(_ room: TVIRoom, participantDidConnect participant: TVIRemoteParticipant) {12participant.delegate = self13}1415func subscribed(to videoTrack: TVIRemoteVideoTrack,16publication: TVIRemoteVideoTrackPublication,17for participant: TVIRemoteParticipant) {18print("Subscribed to video track: \(publication.trackName)")19//Code for starting track rendering goes here.20}2122func unsubscribed(from videoTrack: TVIRemoteVideoTrack,23publication: TVIRemoteVideoTrackPublication,24for participant: TVIRemoteParticipant) {25print("Unsubscribed from video track: \(publication.trackName)")26//Code for stopping track rendering goes here.27}2829func failedToSubscribe(toVideoTrack publication: TVIRemoteVideoTrackPublication,30error: Error,31for participant: TVIRemoteParticipant) {32let errorText = String(describing: error)33print("Failed to subscribe to: \(publication.trackName), error: \(errorText)")34//Code for managing subscribe errors goes here.35}
The following code snippet illustrates how to manage subscribe events in Twilio Video Android SDK. For the sake of simplicity, we have only considered subscription event from video tracks. Callbacks for audio and data tracks are similar but using their corresponding data types.
Java
1ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken).build();23Room room = Video.connect(context, connectOptions, this);45@Override6public void onConnected(@NonNull Room room) {7for (RemoteParticipant remoteParticipant : room.getRemoteParticipants()) {8remoteParticipant.setListener(this);9}10}1112@Override13public void onParticipantConnected(@NonNull Room room,14@NonNull RemoteParticipant remoteParticipant) {15remoteParticipant.setListener(this);16}1718@Override19public void onVideoTrackSubscribed(@NonNull RemoteParticipant remoteParticipant,20@NonNull RemoteVideoTrackPublication remoteVideoTrackPublication,21@NonNull RemoteVideoTrack remoteVideoTrack) {22Log.i("Listener", "Subscribed to video track: " + remoteVideoTrack.getName());23// Code for starting track rendering goes here24}2526@Override27public void onVideoTrackUnsubscribed(@NonNull RemoteParticipant remoteParticipant,28@NonNull RemoteVideoTrackPublication remoteVideoTrackPublication,29@NonNull RemoteVideoTrack remoteVideoTrack) {30Log.i("Listener", "Unsubscribed from video track: " + remoteVideoTrack.getName());31// Code for stopping track rendering goes here32}3334@Override35public void onVideoTrackSubscriptionFailed(@NonNull RemoteParticipant remoteParticipant,36@NonNull RemoteVideoTrackPublication remoteVideoTrackPublication,37@NonNull TwilioException twilioException) {38Log.e("Listener", "Failed to subscribe to: "39+ remoteVideoTrackPublication.getTrackName() + ", error: " +40twilioException.getMessage());41// Code for managing subscribe errors goes here.42}
Kotlin
1val connectOptions = ConnectOptions.Builder(accessToken).build()23val room = Video.connect(context, connectOptions, this)45override fun onConnected(room: Room) {6for (remoteParticipant in room.remoteParticipants) {7remoteParticipant.setListener(this)8}9}1011override fun onParticipantConnected(room: Room,12remoteParticipant: RemoteParticipant) {13remoteParticipant.setListener(this)14}1516override fun onVideoTrackSubscribed(remoteParticipant: RemoteParticipant,17remoteVideoTrackPublication: RemoteVideoTrackPublication,18remoteVideoTrack: RemoteVideoTrack) {19Log.i("Listener", "Subscribed to video track: ${remoteVideoTrack.name}")20// Code for starting track rendering goes here21}2223override fun onVideoTrackUnsubscribed(remoteParticipant: RemoteParticipant,24remoteVideoTrackPublication: RemoteVideoTrackPublication,25remoteVideoTrack: RemoteVideoTrack) {26Log.i("Listener", "Unsubscribed from video track: ${remoteVideoTrack.name}")27// Code for stopping track rendering goes here28}2930override fun onVideoTrackSubscriptionFailed(remoteParticipant: RemoteParticipant,31remoteVideoTrackPublication: RemoteVideoTrackPublication,32twilioException: TwilioException) {33Log.e("Listener", "Failed to subscribe to:" +34"${remoteVideoTrackPublication.trackName}, error: ${twilioException.message}")35// Code for managing subscribe errors goes here.36}
Video contact centers are gaining popularity as an evolution of voice ones. We use this use-case as an example on how to implement an application with static subscribe rules: that is, rules that do change over time. A typical contact center video room may involve the following roles:
The Room communication topology for this use-case is:
Representation of a video contact center where an agent is in helping a customer while being at the same time whispered by a supervisor.
In these types of scenarios where there are clearly established roles and topologies, we recommend the following implementation approach:
agent-video
and the customer
audio track could be named customer-audio
. The specific chosen names are not
relevant. The important point is to comply with the naming convention in all
the video calls.Based on this recommendation, tracks published in this application will look like this:
Agent | Customer | Supervisor | |
---|---|---|---|
Audio Track | MTA_A agent-audio | MTC_A customer-audio | MTS_A whisperer-audio |
Video Track (cam) | MTA_C agent-video | MTC_C customer-video | |
Video Track (screen) | MTC_S customer-screen |
For the sake of simplicity, we assume the following:
MT
followed by the participant initial and by a
letter identifying the track nature (_A
for audio, _C
for webcam, _S
for
screenshare).Agents subscribe to all the tracks published by the rest of participants. Due to this, the default "subscribe-to-all" model is enough. Hence, the application developer does not need add any additional rule:
Use default (i.e. "subscribe-to-all").
Supervisors also subscribe to all the published tracks and the default rule is also enough for them.
Use default (i.e. "subscribe-to-all").
Customers should just subscribe to the agent tracks. We recommend to do it in two steps:
Step 1: override default rule with "subscribe-to-none"
This step avoids race conditions between the default "subscribe-to-all" rule
and POST
setting the appropriate subscribe rules. See section
Overriding the Default Rule for further
information.
Step 2: POST
the appropriate subscribe rules
Use the following code snippet where we assume that:
SKXXXX:your_api_key_secret
RMXXXX
1// NOTE: This example uses the next generation Twilio helper library - for more2// information on how to download and install this version, visit3// https://www.twilio.com/docs/libraries/node45// Find your credentials at twilio.com/console6// To set up environmental variables, see http://twil.io/secure7const API_KEY_SID = process.env.TWILIO_API_KEY;8const API_KEY_SECRET = process.env.TWILIO_API_KEY_SECRET;9const ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;1011const Twilio = require('twilio');1213const client = new Twilio(API_KEY_SID, API_KEY_SECRET, {accountSid: ACCOUNT_SID});1415client.video.rooms('RMXXXX').participants.get('Customer')16.subscribeRules.update({17rules: [18{"type": "include", "all": true},19{"type": "exclude", "publisher": "Supervisor"}20]21})22.then(result => {23console.log('Subscribe Rules updated successfully')24})25.catch(error => {26console.log('Error updating rules ' + error)27});
1{2"room_sid": "RMXXXX",3"participant_sid": "PAXXXX",4"rules": [5{"type": "include", "all": true},6{"type": "exclude", "publisher": "Supervisor"}7],8"date_updated": "2019-04-30T20:28:00Z",9"date_created": "2019-04-30T20:15:49Z"10}
The example above relies on some a prior knowledge about track names and participant roles for setting the subscribe rules at connect time. However, this is not always possible as there might be use-cases where the number of participants, their roles and their communication topology may dynamically change. For example, imagine a collaboration application where participants have full freedom to select the tracks they want to subscribe to at runtime. In this context, imagine a participant named Adam who connects with the following preferences:
The way in which the application signals the different events is out of the scope of this document. However, concentrating only on Track Subscription API calls, this is a possible solution. Notice that we assume that:
SKXXXX:your_api_key_secret
.RMXXXX
.1// NOTE: This example uses the next generation Twilio helper library - for more2// information on how to download and install this version, visit3// https://www.twilio.com/docs/libraries/node45// Find your credentials at twilio.com/console6// To set up environmental variables, see http://twil.io/secure7const API_KEY_SID = process.env.TWILIO_API_KEY;8const API_KEY_SECRET = process.env.TWILIO_API_KEY_SECRET;9const ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;1011const Twilio = require('twilio');1213const client = new Twilio(API_KEY_SID, API_KEY_SECRET, {accountSid: ACCOUNT_SID});1415//-------------------------------------------------------------------------------16//1. At connect time Adam wants to receive all the tracks.17// Done by default rule. No further actions required.181920//-------------------------------------------------------------------------------21//2. After a while, Adam notices his bandwidth consumption is too high and22// decides to unsubscribe from all video tracks2324client.video.rooms('RMXXXX').participants.get('Adam')25.subscribeRules.update({26rules: [27{"type": "include", "kind": "audio"}28]29})30.then(result => {31console.log('Subscribe Rules updated successfully')32})33.catch(error => {34console.log('Error updating rules ' + error)35});3637//-------------------------------------------------------------------------------38//3. Later, a video screenshare track with SID MTXXXX is published to the room39// and Adam subscribes to it.4041client.video.rooms('RMXXXX').participants.get('Adam')42.subscribeRules.update({43rules: [44{"type": "include", "kind": "audio"},45{"type": "include", "track": "MTXXXX"}46]47})48.then(result => {49console.log('Subscribe Rules updated successfully')50})51.catch(error => {52console.log('Error updating rules ' + error)53});5455//-------------------------------------------------------------------------------56//4. John, another participant, is in a noisy place and his audio track is57// annoying. Adam decides to unsubscribe from it.5859client.video.rooms('RMXXXX').participants.get('Adam')60.subscribeRules.update({61rules: [62{"type": "include", "kind": "audio"},63{"type": "include", "track": "MTXXXX"},64{"type": "exclude", "publisher": "John", "kind": "audio"}65]66})67.then(result => {68console.log('Subscribe Rules updated successfully')69})70.catch(error => {71console.log('Error updating rules ' + error)72});
1//-------------------------------------------------------------------------------2//1. At connect time Adam wants to receive all the tracks.3// Done by default rule. No further actions required.456//-------------------------------------------------------------------------------7//2. After a while, Adam notices his bandwidth consumption is too high and8// decides to unsubscribe from all video tracks910{11"room_sid": "RMXXXX",12"participant_sid": "PAXXXX",13"rules": [14{"type": "include", "kind": "audio"}15],16"date_updated": "2019-04-30T20:28:00Z",17"date_created": "2019-04-30T20:15:49Z"18}1920//-------------------------------------------------------------------------------21//3. Later, a video screenshare track with SID MTXXXX is published to the room22// and Adam subscribes to it.2324{25"room_sid": "RMXXXX",26"participant_sid": "PAXXXX",27"rules": [28{"type": "include", "kind": "audio"},29{"type": "include", "track": "MTXXXX"}30],31"date_updated": "2019-04-30T20:28:00Z",32"date_created": "2019-04-30T20:15:49Z"33}3435//-------------------------------------------------------------------------------36//4. John, another participant, is in a noisy place and his audio track is37// annoying. Adam decides to unsubscribe from it.3839{40"room_sid": "RMXXXX",41"participant_sid": "PAXXXX",42"rules": [43{"type": "include", "kind": "audio"},44{"type": "include", "track": "MTXXXX"},45{"type": "exclude", "publisher": "John", "kind": "audio"}46],47"date_updated": "2019-04-30T20:28:00Z",48"date_created": "2019-04-30T20:15:49Z"49}
For executing this example the following is required:
SKXXXX:your_api_key_secret
)RMXXXX
)PAXXXX
)1// NOTE: This example uses the next generation Twilio helper library - for more2// information on how to download and install this version, visit3// https://www.twilio.com/docs/libraries/node45// Find your credentials at twilio.com/console6// To set up environmental variables, see http://twil.io/secure7const API_KEY_SID = process.env.TWILIO_API_KEY;8const API_KEY_SECRET = process.env.TWILIO_API_KEY_SECRET;9const ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;1011const Twilio = require('twilio');1213const client = new Twilio(API_KEY_SID, API_KEY_SECRET, {accountSid: ACCOUNT_SID});1415client.video.rooms('RMXXXX').participants.get('PAXXXX')16.subscribeRules.fetch()17.then(subscribeRules => {18subscribeRules.rules.forEach(rule => {19console.log('Read rule with type = ' + rule.type);20});21})22.catch(error => {23console.log('Error fetching subscribed rules ' + error)24});
1{2"room_sid": "RMXXXX",3"participant_sid": "PAXXXX",4"rules": [5{6"all": true,7"type": "include"8},9{10"publisher": "Carl",11"type": "exclude"12}13],14"date_updated": "2019-04-30T12:38:10Z",15"date_created": "2019-04-30T12:25:21Z"16}
For executing this example the following is required:
SKXXXX:your_api_key_secret
)RMXXXX
)PAXXXX
)1// NOTE: This example uses the next generation Twilio helper library - for more2// information on how to download and install this version, visit3// https://www.twilio.com/docs/libraries/node45// Find your credentials at twilio.com/console6// To set up environmental variables, see http://twil.io/secure7const API_KEY_SID = process.env.TWILIO_API_KEY;8const API_KEY_SECRET = process.env.TWILIO_API_KEY_SECRET;9const ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;1011const Twilio = require('twilio');1213const client = new Twilio(API_KEY_SID, API_KEY_SECRET, {accountSid: ACCOUNT_SID});1415client.video.rooms('RMXXXX').participants.get('PAXXXX')16.subscribedTracks.list()17.then(subscribedTracks => {18subscribedTracks.rules.forEach(subscribedTrack => {19console.log('Read subscribed track with sid = ' + subscribedTrack.sid);20});21})22.catch(error => {23console.log('Error fetching subscribed tracks' + error)24});
1{2"subscribed_tracks": [3{4"kind": "video",5"name": "1f6f07c7-7d02-4f4c-9caa-c7fdd6817d5a",6"date_updated": null,7"sid": "MTXXXX",8"enabled": true,9"room_sid": "RMXXXX",10"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks/MTXXXX",11"date_created": "2019-04-30T12:26:07Z",12"publisher_sid": "PAXYXY",13"participant_sid": "PAXXXX"14},15{16"kind": "audio",17"name": "4ff26a60-3447-4dd3-8281-2f83568bff50",18"date_updated": null,19"sid": "MTYYYY",20"enabled": true,21"room_sid": "RMXXXX",22"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks/MTYYYY",23"date_created": "2019-04-30T12:26:07Z",24"publisher_sid": "PAXYXY",25"participant_sid": "PAXXXX"26},27{28"kind": "video",29"name": "e124298c-f90f-468a-ad33-d7071c058231",30"date_updated": null,31"sid": "MTZZZZ",32"enabled": true,33"room_sid": "RMXXXX",34"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks/MTZZZZ",35"date_created": "2019-04-30T12:25:44Z",36"publisher_sid": "PAYXYX",37"participant_sid": "PAXXXX"38},39{40"kind": "audio",41"name": "ec40db11-aeb6-4d88-879a-0870aaa6c994",42"date_updated": null,43"sid": "MTKKKK",44"enabled": true,45"room_sid": "RMXXXX",46"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks/MTKKKK",47"date_created": "2019-04-30T12:25:44Z",48"publisher_sid": "PAYXYX",49"participant_sid": "PAXXXX"50}51],52"meta": {53"page": 0,54"page_size": 50,55"first_page_url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks?PageSize=50&Page=0",56"previous_page_url": null,57"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks?PageSize=50&Page=0",58"next_page_url": null,59"key": "subscribed_tracks"60}61}
For executing this example the following is required:
SKXXXX:your_api_key_secret
)RMXXXX
)PAXXXX
)MTXXXX
)1// NOTE: This example uses the next generation Twilio helper library - for more2// information on how to download and install this version, visit3// https://www.twilio.com/docs/libraries/node45// Find your credentials at twilio.com/console6// To set up environmental variables, see http://twil.io/secure7const API_KEY_SID = process.env.TWILIO_API_KEY;8const API_KEY_SECRET = process.env.TWILIO_API_KEY_SECRET;9const ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;1011const Twilio = require('twilio');1213const client = new Twilio(API_KEY_SID, API_KEY_SECRET, {accountSid: ACCOUNT_SID});1415client.video.rooms('RMXXXX').participants.get('PAXXXX')16.subscribedTracks.get('MTXXXX')17.fetch()18.then(subscription => {19console.log('Read track subscription with sid = ' + subscription.sid);20})21.catch(error => {22console.log('Error fetching subscribed track' + error)23});
1{2"kind": "video",3"name": "1f6f07c7-7d02-4f4c-9caa-c7fdd6817d5a",4"date_updated": null,5"sid": "MTXXXX",6"enabled": true,7"room_sid": "RMXXXX",8"url": "https://video.twilio.com/v1/Rooms/RMXXXX/Participants/PAXXXX/SubscribedTracks/MTXXXX",9"date_created": "2019-04-30T12:26:07Z",10"publisher_sid": "PAXYXY",11"participant_sid": "PAXXXX"12}
There are no current known issues.