Following on our previous article in the series, let's talk about some of the building blocks for in-call applications. In this installment: placing callers on hold, and retrieving them.
Well, first, we'll start by defining the in-call app:
require 'matrioska/dial_with_apps'
class InboundController < Adhearsion::CallController
include Matrioska::DialWithApps
def run
dial_with_local_apps 'sip:5201996@localphone.com' do |runner, dial|
runner.map_app '1' do
place_on_hold dial
end
end
end
private
def place_on_hold(dial)
logger.info "Splitting calls"
blocker = Celluloid::Condition.new
dial.split main: InCallAppController, others: HoldMusicController, main_callback: ->(call) { blocker.broadcast }
blocker.wait # This prevents the Matrioska listener from starting again until the calls are rejoined
end
end
Woah! We've added a lot of code in this example, what is it all doing? Well, we've replaced our compliments with a call to #place_on_hold
, which is defined, amongst other things, to split the dial operation in half. We're sending the main call, which is the A leg who originally invoked #dial
, to the InCallAppController
, and all other calls to HoldMusicController
(we'll create these controllers in a moment). We also create a Celluloid::Condition
, which blocks the in-call app from finishing until the InCallAppController is finished on our 'main' call.
So what are HoldMusicController
and InCallAppController
? Well, HoldMusicController is fairly self-explanatory, and here's how it's defined:
class HoldMusicController < Adhearsion::CallController
def run
output = play! 'You are on hold.', repeat_times: 1000
call.on_joined { output.stop! }
end
end
This starts an asynchronous playback operation to repeat a thousand times (it should be possible to set this to 0 to make it infinite, but that appears to be broken), and then schedules it to be terminated when the call is joined to another call.
The InCallAppController
is almost as simple:
class InCallAppController < Adhearsion::CallController
def run
say "Hey, the other guy is on hold right now. I'll reconnect you in 5 seconds."
sleep 5
main_dial.rejoin
end
private
def main_dial
metadata['current_dial']
end
end
Here, after playing back a simple message, we just wait for 5 seconds and then rejoin the dial operation back together. How did we get hold of that Dial
operation? Well, it was set in the controller's metadata thanks to Dial#split
, and we defined a private method to easily access this.
Now, after the B leg has been on hold for 5 seconds, the A-leg will rejoin it, the InCallAppController
will finish, and the in-call app will be restarted ready to put the caller on hold again a second time. Snazzy, huh?