In-call apps with Adhearsion (part 2 of 4) - Please hold

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.

Please hold

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?

Ben Langfeld 15 October 2013 Rio de Janeiro, Brasil