Asterisk Bridges¶
In Asterisk, bridges can be thought of as a container for channels that form paths of communication between the channels contained within them. They can be used to pass media back and forth between the channels, as well as to play media to the various channels in a variety of ways.
Tip
For more information on bridges in Asterisk, see Bridges.
Bridges in a Stasis Application¶
Bridge Types¶
When a bridge is created through ARI, there are a number of attributes that can be specified that determine how the bridge mixes media between its participants. These include:
mixing
- specify that media should be passed between all channels in the bridge. This attribute cannot be used withholding
.dtmf_events
- specify that media should be decoded within Asterisk so that DTMF can be recognized. If this is not specified, then DTMF events may not be raised due to the media being passed directly between the channels in the bridge. This attribute only impacts how media is mixed when themixing
attribute is used.proxy_media
- specify that media should always go through Asterisk, even if it could be redirected between clients. This attribute only impacts how media is mixed when themixing
attribute is used. On This PageBridges in Depth*holding
- specify that the channels in the bridge should be entertained with some media. Channels in the bridge have two possible roles: a participant or an announcer. Media between participant channels is not shared; media from an announcer channel is played to all participant channels.
Depending on the combination of attributes selected when a bridge is created, different mixing technologies may be used for the participants in the bridge. Asterisk will attempt to use the most performant mixing technology that it can based on the channel types in the bridge, subject to the attributes specified when the bridge was created.
Subscription Model¶
Unlike channels, bridges in a Stasis application are not automatically subscribed to for events. In order to receive events concerning events for a given bridge, the applications resource must be used to subscribe to the bridge via the POST - /applications/{app_name}/subscription
operation. Events related to channels entering and leaving bridges will be sent without the need to subscribe to them since they are related to a channel in a Stasis application.
Example: Interacting with Bridges¶
For this example, we're going to write an ARI application that will do the following:
- When it connects, it will print out the names of all existing holding bridges. If there are no existing holding bridges, it will create one.
- When a channel enters into its Stasis application, it will be added to the holding bridge and music on hold will be started on the bridge.
Dialplan¶
The dialplan for this will be very straight forward: a simple extension that drops a channel into Stasis.
extensions.conf
[default]
exten => 1000,1,NoOp()
same => n,Answer()
same => n,Stasis(bridge-hold)
same => n,Hangup()
Python¶
For our Python examples, we will rely primarily on the ari-py library. Because the ari
library will emit useful information using Python logging, we should go ahead and set that up as well - for now, a basicConfig
with ERROR
messages displayed should be sufficient. Finally, we'll need to get a client made by initiating a connection to Asterisk. This occurs using the ari.connect
method, where we have to specify three things:
- The HTTP base URI of the Asterisk server to connect to. Here, we assume that this is running on the same machine as the script, and that we're using the default port for Asterisk's HTTP server -
8088
. - The username of the ARI user account to connect as. In this case, we're specifying it as
asterisk
. - The password for the ARI user account. In this case, that's asterisk.
Tip
Modify the connection credentials as appropriate for your server, although many examples will use these credentials.
Please don't use these credentials in production systems!
#!/usr/bin/env python
import ari
import logging
logging.basicConfig(level=logging.ERROR)
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')
Once we've made our connection, our first task is to look for an existing holding bridge - if there is no existing holding bridge - we need to create it. The bridges resource has an operation for listing existing bridges - GET /bridges
. Using ari-py we need to use the operation nickname - list
. We can then use another bridges resource operation to create a holding bridge if none was found - POST /bridges
. Using ari-py, we need to use the operation nickname - create
.
# find or create a holding bridge
bridges = [candidate for candidate in client.bridges.list() if
candidate.json['bridge_type'] == 'holding']
if bridges:
bridge = bridges[0]
print "Using bridge %s" % bridge.id
else:
bridge = client.bridges.create(type='holding')
print "Created bridge %s" % bridge.id
The GET /channels
operation returns back a list of Bridge
resources. Those resources, however, are returned as JSON from the operation, and while the ari-py
library converts the uniqueid
of those into an attribute on the object, it leaves the rest of them in the JSON dictionary.
Our next step involves adding channels that enter our Stasis application to the bridge we either found or created and signaling when a channel leaves our Stasis application. To do that, we need to subscribe for the StasisStart
and StasisEnd
events:
client.on_channel_event('StasisStart', stasis_start_cb)
client.on_channel_event('StasisEnd', stasis_end_cb)
We need two handler functions - stasis_start_cb
for the StasisStart
event and stasis_end_cb
for the StasisEnd
event:
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
channel = channel_obj.get('channel')
print "Channel %s just entered our application, adding it to bridge %s" % (
channel.json.get('name'), bridge.id)
channel.answer()
bridge.addChannel(channel=channel.id)
bridge.startMoh()
def stasis_end_cb(channel, ev):
"""Handler for StasisEnd event"""
print "Channel %s just left our application" % channel.json.get('name')
Finally, we need to tell the client
to run our application. Once we call client.run
, the websocket connection will be made and our application will wait on events infinitely. We can use Ctrl+C
to kill it and break the connection.
bridge-hold.py¶
The full source code for bridge-hold.py
is shown below:
#!/usr/bin/env python
import ari
import logging
logging.basicConfig(level=logging.ERROR)
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')
# find or create a holding bridge
bridges = [candidate for candidate in client.bridges.list() if
candidate.json['bridge_type'] == 'holding']
if bridges:
bridge = bridges[0]
print "Using bridge %s" % bridge.id
else:
bridge = client.bridges.create(type='holding')
print "Created bridge %s" % bridge.id
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
channel = channel_obj.get('channel')
print "Channel %s just entered our application, adding it to bridge %s" % (
channel.json.get('name'), bridge.id)
channel.answer()
bridge.addChannel(channel=channel.id)
bridge.startMoh()
def stasis_end_cb(channel, ev):
"""Handler for StasisEnd event"""
print "Channel %s just left our application" % channel.json.get('name')
client.on_channel_event('StasisStart', stasis_start_cb)
client.on_channel_event('StasisEnd', stasis_end_cb)
client.run(apps='bridge-hold')
bridge-hold.py in action¶
Here, we see the output from the bridge-hold.py
script when a PJSIP channel for endpoint 'alice' enters into the application:
asterisk:~$ python bridge-hold.py
Created bridge 79f3ad78-b124-4d7b-a629-a53b7e7f50cd
Channel PJSIP/alice-00000001 just entered our application, adding it to bridge 79f3ad78-b124-4d7b-a629-a53b7e7f50cd
Channel PJSIP/alice-00000001 just left our application
JavaScript (Node.js)¶
For our JavaScript examples, we will rely primarily on the Node.js ari-client library. We'll need to get a client made by initiating a connection to Asterisk. This occurs using the ari.connect
method, where we have to specify four things:
- The HTTP base URI of the Asterisk server to connect to. Here, we assume that this is running on the same machine as the script, and that we're using the default port for Asterisk's HTTP server -
8088
. - The username of the ARI user account to connect as. In this case, we're specifying it as
asterisk
. - The password for the ARI user account. In this case, that's asterisk.
- A callback that will be called with an error if one occurred, followed by an instance of an ARI client.
Tip
Modify the connection credentials as appropriate for your server, although many examples will use these credentials.
Please don't use these credentials in production systems!
/*jshint node:true */
'use strict';
var ari = require('ari-client');
var util = require('util');
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
// handler for client being loaded
function clientLoaded (err, client) {
if (err) {
throw err;
}
}
Once we've made our connection, our first task is to look for an existing holding bridge - if there is no existing holding bridge - we need to create it. The bridges resource has an operation for listing existing bridges - GET /bridges
. Using ari-client we need to use the operation nickname - list
. We can then use another bridges resource operation to create a holding bridge if none was found - POST /bridges
. Using ari-client, we need to use the operation nickname - create
.
// find or create a holding bridge
var bridge = null;
client.bridges.list(function(err, bridges) {
if (err) {
throw err;
}
bridge = bridges.filter(function(candidate) {
return candidate.bridge_type === 'holding';
})[0];
if (bridge) {
console.log(util.format('Using bridge %s', bridge.id));
} else {
client.bridges.create({type: 'holding'}, function(err, newBridge) {
if (err) {
throw err;
}
bridge = newBridge;
console.log(util.format('Created bridge %s', bridge.id));
});
}
});
The GET /channels
operation returns back a an error if it occurred and a list of Bridge
resources. ari-client
will return a JavaScript object for each Bridge
resource. Properties such as bridge_type
can be accessed on the object directly.
Our next step involves adding channels that enter our Stasis application to the bridge we either found or created and signaling when a channel leaves our Stasis application. To do that, we need to subscribe for the StasisStart
and StasisEnd
events:
We need two callback functions - stasisStart
for the StasisStart
event and stasisEnd
for the StasisEnd
event:
// handler for StasisStart event
function stasisStart(event, channel) {
console.log(util.format(
'Channel %s just entered our application, adding it to bridge %s',
channel.name,
bridge.id));
channel.answer(function(err) {
if (err) {
throw err;
}
bridge.addChannel({channel: channel.id}, function(err) {
if (err) {
throw err;
}
bridge.startMoh(function(err) {
if (err) {
throw err;
}
});
});
});
}
// handler for StasisEnd event
function stasisEnd(event, channel) {
console.log(util.format(
'Channel %s just left our application', channel.name));
}
Finally, we need to tell the client
to start our application. Once we call client.start
, a websocket connection will be established and the client will emit Node.js events as events come in through the websocket. We can use Ctrl+C
to kill it and break the connection.
bridge-hold.js¶
The full source code for bridge-hold.js
is shown below:
/*jshint node:true */
'use strict';
var ari = require('ari-client');
var util = require('util');
ari.connect('http://localhost:8088', 'asterisk', 'asterisk', clientLoaded);
// handler for client being loaded
function clientLoaded (err, client) {
if (err) {
throw err;
}
// find or create a holding bridge
var bridge = null;
client.bridges.list(function(err, bridges) {
if (err) {
throw err;
}
bridge = bridges.filter(function(candidate) {
return candidate.bridge_type === 'holding';
})[0];
if (bridge) {
console.log(util.format('Using bridge %s', bridge.id));
} else {
client.bridges.create({type: 'holding'}, function(err, newBridge) {
if (err) {
throw err;
}
bridge = newBridge;
console.log(util.format('Created bridge %s', bridge.id));
});
}
});
// handler for StasisStart event
function stasisStart(event, channel) {
console.log(util.format(
'Channel %s just entered our application, adding it to bridge %s',
channel.name,
bridge.id));
channel.answer(function(err) {
if (err) {
throw err;
}
bridge.addChannel({channel: channel.id}, function(err) {
if (err) {
throw err;
}
bridge.startMoh(function(err) {
if (err) {
throw err;
}
});
});
});
}
// handler for StasisEnd event
function stasisEnd(event, channel) {
console.log(util.format(
'Channel %s just left our application', channel.name));
}
client.on('StasisStart', stasisStart);
client.on('StasisEnd', stasisEnd);
client.start('bridge-hold');
}
bridge-hold.js in action¶
Here, we see the output from the bridge-hold.js
script when a PJSIP channel for endpoint 'alice' enters into the application: