title: ARI and Bridges: Holding Bridges pageid: 29396218
Holding Bridges¶
Holding bridges are a special type of bridge in Asterisk. The purpose of a holding bridge is to provide a consistent way to place channels when you want the person on the other end of the channel to wait. Asterisk will mix the media to the channel depending on the type of role the channel has within the bridge. Two types of roles are supported:
participant
- the default role for channels in a holding bridge. Media from the bridge is played directly to the channels; however, media from the channels is not played to any other participant.announcer
- if a channel joins a holding bridge as an announcer, the bridge will not play media to the channel. However, all media from the channel will be played to allparticipant
channels in the bridge simultaneously.
Adding a channel as a participant¶
To add a channel as a participant to a holding bridge, you can either not specify a role
(as the participant
role is the default role for holding bridges), or you can specify the participant
role directly:
POST /bridges/{bridge_id}/addChannel?channel=12345
POST /bridges/{bridge_id}/addChannel?channel=12345&role=participant
On This PageAdding a channel as an announcer¶
To add a channel as an announcer to a holding bridge, you must specify a role of announcer
:
!!! tip When is an Announcer channel useful? If you want to simply play back a media file to all participants in a holding bridge, e.g., "your call is important to us, please keep waiting", you can simply initiate a /play
operation on the holding bridge itself. That will perform a playback to all participants in the same fashion as an announcer channel.
An announcer channel is particularly useful when there is someone actually on the other end of the channel, as opposed to a pre-recorded message. For example, you may have a call queue supervisor who wants to let everyone who is waiting for an agent that response times are especially long, but to hold on for a bit longer. Jumping into the holding bridge as an announcer adds a small bit of humanity to the dreaded call queue experience!
Music on hold, media playback, recording, and other such things¶
When dealing with holding bridges, given the particular media rules and channel roles involves, there are some additional catches that you have to be aware when manipulating the bridge:
- Playing music on hold to the bridge will play it for all participants, as well playing media to the bridge. However, you can only do one of those operations - you cannot play media to a holding bridge while you are simultaneously playing music on hold to the bridge. Initiating a
/play
operation on a holding bridge should only be done after stopping the music on hold; likewise, starting music on hold on a bridge with a/play
operation currently in progress will fail. - Recording a holding bridge - while possible - is not terribly interesting. Participant media is dropped - so at best, you'll only record the entertainment that was played to the participants.
There can be only one!¶
You cannot have an announcer channel in a holding bridge at the same time that you perform a play
operation or have music on hold playing to the bridge. Holding bridges do not mix the media between announcers. Since media from the play
operation has to go to all participants, as does your announcer channel's media, the holding bridge will become quite confused about your application's intent.
Example: Infinite wait area¶
Now that we all know that holding bridges are perfect for building what many callers fear - the dreaded waiting area of doom - let's make one! This example ARI application will do the following:
- When a channel enters into the Stasis application, it will be put into a existing holding bridge or a newly created one if none exist.
- Music on hold will be played on the bridge.
- Periodically, the
thnk-u-for-patience
sound will be played to the bridge thanking the users for their patience, which they will need since this holding bridge will never progress beyond this point! - When a channel leaves a holding bridge, if no other channels remain, the bridge will be destroyed.
This example will use a similar structure to the bridge-hold python example. Unlike that example, however, it will use some form of a timer to perform our periodic announcement to the holding bridge, and when all the channels have left the infinite wait area, we'll destroy the holding bridge (cleaning up resources is always good!)
Dialplan¶
For this example, we need to just drop the channel into Stasis, specifying our application:
extensions.conf
Python¶
When a channel enters our Stasis application, we first look for an existing holding bridge or create one if none is found. When we create a new bridge, we start music on hold in the bridge and create a timer that will call a callback after 30 seconds. That callback temporarily stops the music on hold, and starts a play operation on the bridge that thanks everyone for their patience. When the play operation finishes, it resumes music on hold.
# find or create a holding bridge
holding_bridge = None
# Announcer timer
announcer_timer = None
def find_or_create_bridge():
"""Find our infinite wait bridge, or create a new one
Returns:
The one and only holding bridge
"""
global holding_bridge
global announcer_timer
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 %s" % bridge.id
else:
bridge = client.bridges.create(type='holding')
bridge.startMoh()
print "Created bridge %s" % bridge.id
def play_announcement(bridge):
"""Play an announcement to the bridge"""
def on_playback_finished(playback, ev):
"""Handler for the announcement's PlaybackFinished event"""
global announcer_timer
global holding_bridge
holding_bridge.startMoh()
announcer_timer = threading.Timer(30, play_announcement,
[holding_bridge])
announcer_timer.start()
bridge.stopMoh()
print "Letting the everyone know we care..."
thanks_playback = bridge.play(media='sound:thnk-u-for-patience')
thanks_playback.on_event('PlaybackFinished', on_playback_finished)
holding_bridge = bridge
holding_bridge.on_event('ChannelLeftBridge', on_channel_left_bridge)
# After 30 seconds, let everyone in the bridge know that we care
announcer_timer = threading.Timer(30, play_announcement, [holding_bridge])
announcer_timer.start()
return bridge
The function that does this work, find_or_create_bridge
, is called from our StasisStart
event handler. The bridge that it returns will have the new channel added to it.
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
bridge = find_or_create_bridge()
channel = channel_obj.get('channel')
print "Channel %s just entered our application, adding it to bridge %s" % (
channel.json.get('name'), holding_bridge.id)
channel.answer()
bridge.addChannel(channel=channel.id)
In the find_or_create_bridge
function, we also subscribed for the ChannelLeftBridge
event. We'll add a callback handler for this in that function as well. When the channel leaves the bridge, we'll check to see if there are no more channels in the bridge and - if so - destroy the bridge.
def on_channel_left_bridge(bridge, ev):
"""Handler for ChannelLeftBridge event"""
global holding_bridge
global announcer_timer
channel = ev.get('channel')
channel_count = len(bridge.json.get('channels'))
print "Channel %s left bridge %s" % (channel.get('name'), bridge.id)
if holding_bridge.id == bridge.id and channel_count == 0:
if announcer_timer:
announcer_timer.cancel()
announcer_timer = None
print "Destroying bridge %s" % bridge.id
holding_bridge.destroy()
holding_bridge = None
bridge-infinite-wait.py¶
The full source code for bridge-infinite-wait.py
is shown below:
bridge-infinite-wait.py
#!/usr/bin/env python
import ari
import logging
import threading
logging.basicConfig(level=logging.ERROR)
client = ari.connect('http://localhost:8088', 'asterisk', 'asterisk')
# find or create a holding bridge
holding_bridge = None
# Announcer timer
announcer_timer = None
def find_or_create_bridge():
"""Find our infinite wait bridge, or create a new one
Returns:
The one and only holding bridge
"""
global holding_bridge
global announcer_timer
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 %s" % bridge.id
else:
bridge = client.bridges.create(type='holding')
bridge.startMoh()
print "Created bridge %s" % bridge.id
def play_announcement(bridge):
"""Play an announcement to the bridge"""
def on_playback_finished(playback, ev):
"""Handler for the announcement's PlaybackFinished event"""
global announcer_timer
global holding_bridge
holding_bridge.startMoh()
announcer_timer = threading.Timer(30, play_announcement,
[holding_bridge])
announcer_timer.start()
bridge.stopMoh()
print "Letting the everyone know we care..."
thanks_playback = bridge.play(media='sound:thnk-u-for-patience')
thanks_playback.on_event('PlaybackFinished', on_playback_finished)
def on_channel_left_bridge(bridge, ev):
"""Handler for ChannelLeftBridge event"""
global holding_bridge
global announcer_timer
channel = ev.get('channel')
channel_count = len(bridge.json.get('channels'))
print "Channel %s left bridge %s" % (channel.get('name'), bridge.id)
if holding_bridge.id == bridge.id and channel_count == 0:
if announcer_timer:
announcer_timer.cancel()
announcer_timer = None
print "Destroying bridge %s" % bridge.id
holding_bridge.destroy()
holding_bridge = None
holding_bridge = bridge
holding_bridge.on_event('ChannelLeftBridge', on_channel_left_bridge)
# After 30 seconds, let everyone in the bridge know that we care
announcer_timer = threading.Timer(30, play_announcement, [holding_bridge])
announcer_timer.start()
return bridge
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
bridge = find_or_create_bridge()
channel = channel_obj.get('channel')
print "Channel %s just entered our application, adding it to bridge %s" % (
channel.json.get('name'), holding_bridge.id)
channel.answer()
bridge.addChannel(channel=channel.id)
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-infinite-wait')
bridge-infinite-wait.py in action¶
Created bridge 950c4805-c33c-4895-ad9a-2798055e4939
Channel PJSIP/alice-00000000 just entered our application, adding it to bridge 950c4805-c33c-4895-ad9a-2798055e4939
Letting the everyone know we care...
Channel PJSIP/alice-00000000 left bridge 950c4805-c33c-4895-ad9a-2798055e4939
Destroying bridge 950c4805-c33c-4895-ad9a-2798055e4939
Channel PJSIP/alice-00000000 just left our application
JavaScript (Node.js)¶
When a channel enters our Stasis application, we first look for an existing holding bridge or create one if none is found. When we create a new bridge, we start music on hold in the bridge and create a timer that will call a callback after 30 seconds. That callback temporarily stops the music on hold, and starts a play operation on the bridge that thanks everyone for their patience. When the play operation finishes, it resumes music on hold.
In all cases, we add the channel to the bridge via the joinBridge
function.
console.log('Channel %s just entered our application', channel.name);
// find or create a holding bridge
client.bridges.list(function(err, bridges) {
if (err) {
throw err;
}
var bridge = bridges.filter(function(candidate) {
return candidate.bridge_type === 'holding';
})[0];
if (bridge) {
console.log('Using bridge %s', bridge.id);
joinBridge(bridge);
} else {
client.bridges.create({type: 'holding'}, function(err, newBridge) {
if (err) {
throw err;
}
console.log('Created bridge %s', newBridge.id);
newBridge.startMoh(function(err) {
if (err) {
throw err;
}
});
joinBridge(newBridge);
timer = setTimeout(play_announcement, 30000);
// callback that will let our users know how much we care
function play_announcement() {
console.log('Letting everyone know we care...');
newBridge.stopMoh(function(err) {
if (err) {
throw err;
}
var playback = client.Playback();
newBridge.play({media: 'sound:thnk-u-for-patience'},
playback, function(err, playback) {
if (err) {
throw err;
}
});
playback.once('PlaybackFinished', function(event, playback) {
newBridge.startMoh(function(err) {
if (err) {
throw err;
}
});
timer = setTimeout(play_announcement, 30000);
});
});
}
});
}
The joinBridge function involves registered a callback for the ChannelLeftBridge event and adds the channel to the bridge.
function joinBridge(bridge) {
channel.once('ChannelLeftBridge', function(event, instances) {
channelLeftBridge(event, instances, bridge);
});
bridge.addChannel({channel: channel.id}, function(err) {
if (err) {
throw err;
}
});
channel.answer(function(err) {
if (err) {
throw err;
}
});
}
Notice that we use an anonymous function to pass the bridge as an extra parameter to the ChannelLeftBridge callback so we can keep the handler at the same level as joinBridge and avoid another indentation level of callbacks. Finally, we can handle destroying the bridge when the last channel contained in it has left:
// Handler for ChannelLeftBridge event
function channelLeftBridge(event, instances, bridge) {
var holdingBridge = instances.bridge;
var channel = instances.channel;
console.log('Channel %s left bridge %s', channel.name, bridge.id);
if (holdingBridge.id === bridge.id &&
holdingBridge.channels.length === 0) {
if (timer) {
clearTimeout(timer);
}
bridge.destroy(function(err) {
if (err) {
throw err;
}
});
}
}
bridge-infinite-wait.js¶
The full source code for bridge-infinite-wait.js
is shown below:
bridge-infinite-wait.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 |
|
bridge-infinite-wait.js in action¶
The following shows the output of the bridge-infinite-wait.js
script when a PJSIP
channel for alice
enters the application: