title: ARI and Bridges: Bridge Operations pageid: 29396222
Moving Between Bridges¶
Channels can be both added and removed from bridges via the POST - /bridges/{bridgeId}/addChannel
and POST - /bridges/{bridgeId}/removeChannel
operations. This allows channels to be put in a holding bridge while waiting for an application to continue to its next step for example. One example of this would be to put an incoming channel into a holding bridge playing music on hold while dialing another endpoint. Once that endpoint answers, the incoming channel can be moved from the holding bridge to a mixing bridge to establish an audio call between the two channels.
Example: Dialing with Entertainment¶
This example ARI application will do the following:
- When a channel enters into the Stasis application, it will be put in a holding bridge and a call will be originated to the endpoint specified by the first command line argument to the script.
- When that channel enters into the Stasis application, the original channel will be removed from the holding bridge, a mixing bridge will be created, and the two channels will be put in it.
- If either channel hangs up, the other channel will also be hung up.
- Once the dialed channel exists the Stasis application, the mixing bridge will be destroyed. On This Page
Dialplan¶
For this example, we need to just drop the channel into Stasis, specifying our application:
extensions.conf
Python¶
A large part of the implementation of this particular example is similar to the bridge-dial.py
example. However, instead of ringing the inbound channel, we'll instead create a holding bridge and place the channel in said holding bridge. Since a holding bridge can hold a number of channels, we'll reuse the same holding bridge for all of the channels that use the application. The method to obtain the holding bridge is find_or_create_holding_bridge
, shown below:
# Our one and only holding bridge
holding_bridge = None
def find_or_create_holding_bridge():
"""Find our infinite wait bridge, or create a new one
Returns:
The one and only holding bridge
"""
global holding_bridge
if holding_bridge:
return holding_bridge
bridges = [candidate for candidate in client.bridges.list() if
candidate.json['bridge_type'] == 'holding']
if bridges:
bridge = bridges[0]
print "Using bridge {}".format(bridge.id)
else:
bridge = client.bridges.create(type='holding')
bridge.startMoh()
print "Created bridge {}".format(bridge.id)
holding_bridge = bridge
return holding_bridge
When the inbound channel enters the application, we'll place it into our waiting bridge:
When the dialed channel answers, we can remove the inbound channel from the waiting bridge - since there is only one waiting bridge being used, we can use find_or_create_holding_bridge
to obtain it. We then place it into a newly created mixing bridge along with the dialed channel, in the same fashion as the bridge-dial.py
example.
print "{} answered; bridging with {}".format(outgoing.json.get('name'),
channel.json.get('name'))
wait_bridge = find_or_create_holding_bridge()
wait_bridge.removeChannel(channel=channel.id)
bridge = client.bridges.create(type='mixing')
bridge.addChannel(channel=[channel.id, outgoing.id])
bridge-move.py¶
The full source code for bridge-move.py
is shown below:
#!/usr/bin/env python
import logging
import requests
import ari
logging.basicConfig(level=logging.ERROR)
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')
# Our one and only holding bridge
holding_bridge = None
def find_or_create_holding_bridge():
"""Find our infinite wait bridge, or create a new one
Returns:
The one and only holding bridge
"""
global holding_bridge
if holding_bridge:
return holding_bridge
bridges = [candidate for candidate in client.bridges.list() if
candidate.json['bridge_type'] == 'holding']
if bridges:
bridge = bridges[0]
print "Using bridge {}".format(bridge.id)
else:
bridge = client.bridges.create(type='holding')
bridge.startMoh()
print "Created bridge {}".format(bridge.id)
holding_bridge = bridge
return holding_bridge
def safe_hangup(channel):
"""Safely hang up the specified channel"""
try:
channel.hangup()
print "Hung up {}".format(channel.json.get('name'))
except requests.HTTPError as e:
if e.response.status_code != requests.codes.not_found:
raise e
def safe_bridge_destroy(bridge):
"""Safely destroy the specified bridge"""
try:
bridge.destroy()
except requests.HTTPError as e:
if e.response.status_code != requests.codes.not_found:
raise e
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart"""
channel = channel_obj.get('channel')
channel_name = channel.json.get('name')
args = ev.get('args')
if not args:
print "Error: {} didn't provide any arguments!".format(channel_name)
return
if args and args[0] != 'inbound':
# Only handle inbound channels here
return
if len(args) != 2:
print "Error: {} didn't tell us who to dial".format(channel_name)
channel.hangup()
return
wait_bridge = find_or_create_holding_bridge()
wait_bridge.addChannel(channel=channel.id)
try:
outgoing = client.channels.originate(endpoint=args[1],
app='bridge-move',
appArgs='dialed')
except requests.HTTPError:
print "Whoops, pretty sure %s wasn't valid" % args[1]
channel.hangup()
return
channel.on_event('StasisEnd', lambda \*args: safe_hangup(outgoing))
outgoing.on_event('StasisEnd', lambda \*args: safe_hangup(channel))
def outgoing_start_cb(channel_obj, ev):
"""StasisStart handler for our dialed channel"""
print "{} answered; bridging with {}".format(outgoing.json.get('name'),
channel.json.get('name'))
wait_bridge = find_or_create_holding_bridge()
wait_bridge.removeChannel(channel=channel.id)
bridge = client.bridges.create(type='mixing')
bridge.addChannel(channel=[channel.id, outgoing.id])
# Clean up the bridge when done
channel.on_event('StasisEnd', lambda \*args:
safe_bridge_destroy(bridge))
outgoing.on_event('StasisEnd', lambda \*args:
safe_bridge_destroy(bridge))
outgoing.on_event('StasisStart', outgoing_start_cb)
client.on_channel_event('StasisStart', stasis_start_cb)
client.run(apps='bridge-move')
bridge-move.py in action¶
The following shows the output of the bridge-move.py
script when a PJSIP
channel for alice
enters the application and dials a PJSIP channel for bob:
PJSIP/Alice-00000001 entered our application
Dialing PJSIP/Bob
PJSIP/Bob-00000002 answered; bridging with PJSIP/Alice-00000001
Hung up PJSIP/Bob-00000002
JavaScript (Node.js)¶
This example is very similar to bridge-dial.js with one main difference: the original Stasis channel is put in a holding bridge while the an originate operation is used to dial another channel. Once the dialed channel enters into the Stasis application, the original channel will be removed from the holding bridge, and both channels will finally be put into a mixing bridge. Once a channel enters into our Stasis application, we either find an existing holding bridge or create one:
function findOrCreateHoldingBridge(channel) {
client.bridges.list(function(err, bridges) {
var holdingBridge = bridges.filter(function(candidate) {
return candidate.bridge_type === 'holding';
})[0];
if (holdingBridge) {
console.log('Using existing holding bridge %s', holdingBridge.id);
originate(channel, holdingBridge);
} else {
client.bridges.create({type: 'holding'}, function(err, holdingBridge) {
if (err) {
throw err;
}
console.log('Created new holding bridge %s', holdingBridge.id);
originate(channel, holdingBridge);
});
}
});
}
We then add the channel to the holding bridge and start music on hold before continuing with dialing we we did in the bridge-dial.js example:
holdingBridge.addChannel({channel: channel.id}, function(err) {
if (err) {
throw err;
}
holdingBridge.startMoh(function(err) {
// ignore error
});
});
Once the endpoint has answered and a mixing bridge has been created, we proceed by first removing the original channel from the holding bridge and then adding both channels to the mixing bridge as before:
function moveToMixingBridge(channel, dialed, mixingBridge, holdingBridge) {
console.log('Adding channel %s and dialed channel %s to bridge %s',
channel.name, dialed.name, mixingBridge.id);
holdingBridge.removeChannel({channel: channel.id}, function(err) {
if (err) {
throw err;
}
mixingBridge.addChannel(
{channel: [channel.id, dialed.id]}, function(err) {
if (err) {
throw err;
}
});
});
}
Note that we need to keep track of one more variable as we go down the application flow to ensure we have a reference to both the holding and mixing bridge. Again we use anonymous functions to pass extra arguments to callback handlers to keep the nested callbacks to a minimum.
bridge-move.js¶
The full source code for bridge-move.js
is shown below:
bridge-move.js | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
|
bridge-move.js in action¶
The following shows the output of the bridge-move.js
script when a PJSIP
channel for alice
enters the application and dials a PJSIP channel for bob:
Channel PJSIP/alice-00000001 has entered our application
Created new holding bridge e58641af-2006-4c3d-bf9e-8817baa27381
Created mixing bridge 5ae49fee-e353-4ad9-bfa7-f8306d9dfd1e
Adding channel PJSIP/alice-00000001 and dialed channel PJSIP/bob-00000002 to bridge 5ae49fee-e353-4ad9-bfa7-f8306d9dfd1e
Dialed channel PJSIP/bob-00000002 has left our application, destroying mixing bridge 5ae49fee-e353-4ad9-bfa7-f8306d9dfd1e
Hanging up channel PJSIP/alice-00000001
Hanging up channel undefined