class Adhearsion::Call

Encapsulates call-related data and behavior.

Constants

CommandTimeout
ExpiredError
Hangup

Attributes

after_hangup_lifetime[RW]

@return [Integer] the number of seconds after the call is hung up that the controller will remain active

auto_hangup[RW]

@return [true, false] whether or not the call should be automatically hung up after executing its controller

controllers[R]

@return [Array<Adhearsion::CallController>] the set of call controllers executing on the call

end_code[R]

@return [String] the reason code for the call ending

end_reason[R]

@return [Symbol] the reason for the call ending

end_time[R]

@return [Time] the time at which the call began. For inbound calls this is the time at which the call was offered to Adhearsion. For outbound calls it is the time at which the remote party answered.

start_time[R]

@return [Time] the time at which the call began. For inbound calls this is the time at which the call was offered to Adhearsion. For outbound calls it is the time at which the remote party answered.

variables[R]

@return [Hash<String => String>] a collection of SIP headers set during the call

Public Class Methods

new(offer = nil) click to toggle source
# File lib/adhearsion/call.rb, line 77
def initialize(offer = nil)
  register_initial_handlers

  @offer        = nil
  @tags         = []
  @commands     = CommandRegistry.new
  @variables    = HashWithIndifferentAccess.new
  @controllers  = []
  @end_reason   = nil
  @end_code     = nil
  @end_blocker  = Celluloid::Condition.new
  @peers        = {}
  @duration     = nil
  @auto_hangup  = true
  @after_hangup_lifetime = nil
  @call_terminating = false

  self << offer if offer
end
uri(transport, id, domain) click to toggle source
# File lib/adhearsion/call.rb, line 68
def self.uri(transport, id, domain)
  return nil unless id
  s = ""
  s << transport << ":" if transport
  s << id
  s << "@" << domain if domain
  s
end

Public Instance Methods

<<(message)
Alias for: deliver_message
accept(headers = nil) click to toggle source
# File lib/adhearsion/call.rb, line 323
def accept(headers = nil)
  @accept_command ||= write_and_await_response Punchblock::Command::Accept.new(:headers => headers)
rescue Punchblock::ProtocolError => e
  abort e
end
active?() click to toggle source

@return [Boolean] if the call is currently active or not (disconnected)

# File lib/adhearsion/call.rb, line 319
def active?
  !end_reason
end
answer(headers = nil) click to toggle source
# File lib/adhearsion/call.rb, line 329
def answer(headers = nil)
  write_and_await_response Punchblock::Command::Answer.new(:headers => headers)
rescue Punchblock::ProtocolError => e
  abort e
end
clear_from_active_calls() click to toggle source

@private

# File lib/adhearsion/call.rb, line 372
def clear_from_active_calls
  Adhearsion.active_calls.remove_inactive_call current_actor
end
commands() click to toggle source
# File lib/adhearsion/call.rb, line 197
def commands
  @commands.clone
end
deliver_message(message) click to toggle source
# File lib/adhearsion/call.rb, line 189
def deliver_message(message)
  logger.debug "Receiving message: #{message.inspect}"
  catching_standard_errors do
    trigger_handler :event, message, broadcast: true, exception_callback: ->(e) { Adhearsion::Events.trigger :exception, [e, logger] }
  end
end
Also aliased as: <<
domain() click to toggle source

@return [String, nil] The domain on which the call resides

# File lib/adhearsion/call.rb, line 108
def domain
  offer.domain if offer
end
duration() click to toggle source

@return [Float] The call duration until the current time, or until the call was disconnected, whichever is earlier

# File lib/adhearsion/call.rb, line 261
def duration
  if @duration
    @duration
  elsif @start_time
    Time.now - @start_time
  else
    0.0
  end
end
execute_controller(controller = nil, completion_callback = nil, &block) click to toggle source

Execute a call controller asynchronously against this call.

To block and wait until the controller completes, call `#join` on the result of this method.

@param [Adhearsion::CallController] controller an instance of a controller initialized for this call @param [Proc] a callback to be executed when the controller finishes execution

@yield execute the current block as the body of a controller by specifying no controller instance

@return [Celluloid::ThreadHandle]

# File lib/adhearsion/call.rb, line 551
def execute_controller(controller = nil, completion_callback = nil, &block)
  raise ArgumentError, "Cannot supply a controller and a block at the same time" if controller && block_given?
  controller ||= CallController.new current_actor, &block
  logger.info "Executing controller #{controller.inspect}"
  controller.bg_exec completion_callback
end
guards_for_target(target) click to toggle source

@private

# File lib/adhearsion/call.rb, line 296
def guards_for_target(target)
  target ? [target_from_join_options(join_options_with_target(target))] : []
end
hangup(headers = nil) click to toggle source
# File lib/adhearsion/call.rb, line 362
def hangup(headers = nil)
  return false unless active?
  logger.info "Hanging up"
  @end_reason = true
  write_and_await_response Punchblock::Command::Hangup.new(:headers => headers)
rescue Punchblock::ProtocolError => e
  abort e
end
id() click to toggle source

@return [String, nil] The globally unique ID for the call

# File lib/adhearsion/call.rb, line 100
def id
  offer.target_call_id if offer
end
Also aliased as: to_s
inspect() click to toggle source

@private

# File lib/adhearsion/call.rb, line 531
def inspect
  return "..." if Celluloid.detect_recursion
  attrs = [:offer, :end_reason, :commands, :variables, :controllers, :to, :from].map do |attr|
    "#{attr}=#{send(attr).inspect}"
  end
  "#<#{self.class}:#{id}@#{domain} #{attrs.join ', '}>"
end
join(target, options = {}) click to toggle source

Joins this call to another call or a mixer

@param [Call, String, Hash] target the target to join to. May be a Call object, a call ID (String, Hash) or a mixer name (Hash) @option target [String] call_uri The call ID to join to @option target [String] mixer_name The mixer to join to @param [Hash, Optional] options further options to be joined with

@return [Hash] where :command is the issued command, :joined_waiter is a wait responder which is triggered when the join is complete, and :unjoined_waiter is a wait responder which is triggered when the entities are unjoined

# File lib/adhearsion/call.rb, line 386
def join(target, options = {})
  logger.debug "Joining to #{target}"

  joined_condition = CountDownLatch.new(1)
  on_joined target do
    joined_condition.countdown!
  end

  unjoined_condition = CountDownLatch.new(1)
  on_unjoined target do
    unjoined_condition.countdown!
  end

  on_end do
    joined_condition.countdown!
    unjoined_condition.countdown!
  end

  command = Punchblock::Command::Join.new options.merge(join_options_with_target(target))
  write_and_await_response command
  {command: command, joined_condition: joined_condition, unjoined_condition: unjoined_condition}
rescue Punchblock::ProtocolError => e
  abort e
end
join_options_with_target(target) click to toggle source

@private

# File lib/adhearsion/call.rb, line 427
def join_options_with_target(target)
  case target
  when nil
    {}
  when Call
    { :call_uri => target.uri }
  when String
    { :call_uri => self.class.uri(transport, target, domain) }
  when Hash
    abort ArgumentError.new "You cannot specify both a call URI and mixer name" if target.has_key?(:call_uri) && target.has_key?(:mixer_name)
    target
  else
    abort ArgumentError.new "Don't know how to join to #{target.inspect}"
  end
end
logger_id() click to toggle source

@private

# File lib/adhearsion/call.rb, line 527
def logger_id
  "#{self.class}: #{id}@#{domain}"
end
mute() click to toggle source
# File lib/adhearsion/call.rb, line 464
def mute
  write_and_await_response Punchblock::Command::Mute.new
rescue Punchblock::ProtocolError => e
  abort e
end
on_end(&block) click to toggle source
# File lib/adhearsion/call.rb, line 300
def on_end(&block)
  register_event_handler Punchblock::Event::End, &block
end
on_joined(target = nil, &block) click to toggle source

Registers a callback for when this call is joined to another call or a mixer

@param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash) @option target [String] call_uri The call ID to guard on @option target [String] mixer_name The mixer name to guard on

# File lib/adhearsion/call.rb, line 278
def on_joined(target = nil, &block)
  register_event_handler Punchblock::Event::Joined, *guards_for_target(target) do |event|
    block.call event
  end
end
on_unjoined(target = nil, &block) click to toggle source

Registers a callback for when this call is unjoined from another call or a mixer

@param [Call, String, Hash, nil] target the target to guard on. May be a Call object, a call ID (String, Hash) or a mixer name (Hash) @option target [String] call_uri The call ID to guard on @option target [String] mixer_name The mixer name to guard on

# File lib/adhearsion/call.rb, line 291
def on_unjoined(target = nil, &block)
  register_event_handler Punchblock::Event::Unjoined, *guards_for_target(target), &block
end
pause_controllers() click to toggle source

@private

# File lib/adhearsion/call.rb, line 564
def pause_controllers
  controllers.each(&:pause!)
end
peers() click to toggle source

Hash of joined peers @return [Hash<String => Adhearsion::Call>]

# File lib/adhearsion/call.rb, line 158
def peers
  @peers.clone
end
redirect(to, headers = nil) click to toggle source

Redirect the call to some other target system.

If the redirect is successful, the call will be released from the telephony engine and Adhearsion will lose control of the call.

Note that for the common case, this will result in a SIP 302 or SIP REFER, which provides the caller with a new URI to dial. As such, the redirect target cannot be any telephony-engine specific address (such as sofia/gateway, agent/101, or SIP/mypeer); instead it should be a fully-qualified external SIP URI that the caller can independently reach.

@param [String] to the target to redirect to, eg a SIP URI @param [Hash, optional] headers a set of headers to send along with the redirect instruction

# File lib/adhearsion/call.rb, line 356
def redirect(to, headers = nil)
  write_and_await_response Punchblock::Command::Redirect.new(to: to, headers: headers)
rescue Punchblock::ProtocolError => e
  abort e
end
register_controller(controller) click to toggle source

@private

# File lib/adhearsion/call.rb, line 559
def register_controller(controller)
  @controllers << controller
end
register_event_handler(*guards, &block) click to toggle source

Register a handler for events on this call. Note that Adhearsion::Call implements the has-guarded-handlers API, and all of its methods are available. Specifically, all Adhearsion events are available on the `:event` channel.

@param [guards] guards take a look at the guards documentation

@yield [Object] trigger_object the incoming event

@return [String] handler ID for later manipulation

@see adhearsion.github.io/has-guarded-handlers for more details

# File lib/adhearsion/call.rb, line 185
def register_event_handler(*guards, &block)
  register_handler :event, *guards, &block
end
register_initial_handlers() click to toggle source

@private

# File lib/adhearsion/call.rb, line 202
def register_initial_handlers
  register_event_handler Punchblock::Event::Offer do |offer|
    @offer  = offer
    @client = offer.client
    @start_time = offer.timestamp.to_time
  end

  register_event_handler Punchblock::HasHeaders do |event|
    merge_headers event.headers
  end

  register_event_handler Punchblock::Event::Complete do |event|
    if event.reason.is_a? Punchblock::Event::Complete::Hangup
      terminating! unless terminating?
    end
  end

  on_joined do |event|
    if event.call_uri
      target = event.call_uri
      type = :call
    else
      target = event.mixer_name
      type = :mixer
    end
    logger.info "Joined to #{type} #{target}"
    call = Adhearsion.active_calls.with_uri(target)
    @peers[target] = call
    signal :joined, target
  end

  on_unjoined do |event|
    if event.call_uri
      target = event.call_uri
      type = :call
    else
      target = event.mixer_name
      type = :mixer
    end
    logger.info "Unjoined from #{type} #{target}"
    @peers.delete target
    signal :unjoined, target
  end

  on_end do |event|
    logger.info "Call #{from} -> #{to} ended due to #{event.reason}#{" (code #{event.platform_code})" if event.platform_code}"
    terminating! unless terminating?
    @end_time = event.timestamp.to_time
    @duration = @end_time - @start_time if @start_time
    clear_from_active_calls
    @end_reason = event.reason
    @end_code = event.platform_code
    @end_blocker.broadcast event.reason
    @commands.terminate
    after(@after_hangup_lifetime || Adhearsion.config.platform.after_hangup_lifetime) { terminate }
  end
end
reject(reason = :busy, headers = nil) click to toggle source
# File lib/adhearsion/call.rb, line 335
def reject(reason = :busy, headers = nil)
  write_and_await_response Punchblock::Command::Reject.new(:reason => reason, :headers => headers)
  Adhearsion::Events.trigger_immediately :call_rejected, call: current_actor, reason: reason
rescue Punchblock::ProtocolError => e
  abort e
end
remove_tag(label) click to toggle source

Remove a label

@param [String, Symbol] label

# File lib/adhearsion/call.rb, line 141
def remove_tag(label)
  @tags.reject! { |tag| tag == label }
end
resume_controllers() click to toggle source

@private

# File lib/adhearsion/call.rb, line 569
def resume_controllers
  controllers.each(&:resume!)
end
send_message(body, options = {}) click to toggle source

Sends a message to the caller

@param [String] body The message text. @param [Hash, Optional] options The message options. @option options [String] subject The message subject.

# File lib/adhearsion/call.rb, line 521
def send_message(body, options = {})
  logger.debug "Sending message: #{body}"
  client.send_message id, domain, body, options
end
tag(label) click to toggle source

Tag a call with an arbitrary label

@param [String, Symbol] label String or Symbol with which to tag this call

# File lib/adhearsion/call.rb, line 131
def tag(label)
  abort ArgumentError.new "Tag must be a String or Symbol" unless [String, Symbol].include?(label.class)
  @tags << label
end
tagged_with?(label) click to toggle source

Establish if the call is tagged with the provided label

@param [String, Symbol] label

# File lib/adhearsion/call.rb, line 150
def tagged_with?(label)
  @tags.include? label
end
tags() click to toggle source

@return [Array] The set of labels with which this call has been tagged.

# File lib/adhearsion/call.rb, line 122
def tags
  @tags.clone
end
target_from_join_options(options) click to toggle source

@private

# File lib/adhearsion/call.rb, line 444
def target_from_join_options(options)
  call_uri = options[:call_uri]
  return {call_uri: call_uri} if call_uri
  {mixer_name: options[:mixer_name]}
end
terminating!() click to toggle source
# File lib/adhearsion/call.rb, line 304
def terminating!
  logger.debug "Call is terminating"
  @call_terminating = true
end
terminating?() click to toggle source

@return [Boolean] if the call is currently terminating/terminated

# File lib/adhearsion/call.rb, line 312
def terminating?
  !active? || @call_terminating
end
to_s()
Alias for: id
unjoin(target = nil) click to toggle source

Unjoins this call from another call or a mixer

@param [Call, String, Hash, nil] target the target to unjoin from. May be a Call object, a call ID (String, Hash), a mixer name (Hash) or missing to unjoin from every existing join (nil) @option target [String] call_uri The call ID to unjoin from @option target [String] mixer_name The mixer to unjoin from

# File lib/adhearsion/call.rb, line 418
def unjoin(target = nil)
  logger.info "Unjoining from #{target}"
  command = Punchblock::Command::Unjoin.new join_options_with_target(target)
  write_and_await_response command
rescue Punchblock::ProtocolError => e
  abort e
end
unmute() click to toggle source
# File lib/adhearsion/call.rb, line 470
def unmute
  write_and_await_response Punchblock::Command::Unmute.new
rescue Punchblock::ProtocolError => e
  abort e
end
uri() click to toggle source

@return [String, nil] The uri at which the call resides

# File lib/adhearsion/call.rb, line 115
def uri
  self.class.uri(transport, id, domain)
end
wait_for_end() click to toggle source

Wait for the call to end. Returns immediately if the call has already ended, else blocks until it does so. @return [Symbol] the reason for the call ending

# File lib/adhearsion/call.rb, line 166
def wait_for_end
  if end_reason
    end_reason
  else
    @end_blocker.wait
  end
end
wait_for_joined(expected_target) click to toggle source
# File lib/adhearsion/call.rb, line 450
def wait_for_joined(expected_target)
  target = nil
  until target == expected_target do
    target = wait :joined
  end
end
wait_for_unjoined(expected_target) click to toggle source
# File lib/adhearsion/call.rb, line 457
def wait_for_unjoined(expected_target)
  target = nil
  until target == expected_target do
    target = wait :unjoined
  end
end
write_and_await_response(command, timeout = 60, fatal = false) click to toggle source

@private

# File lib/adhearsion/call.rb, line 477
def write_and_await_response(command, timeout = 60, fatal = false)
  @commands << command
  write_command command

  error_handler = fatal ? ->(error) { raise error } : ->(error) { abort error }

  response = defer { command.response timeout }
  case response
  when Punchblock::ProtocolError
    if response.name == :item_not_found
      error_handler[Hangup.new(@end_reason)]
    else
      error_handler[response]
    end
  when Exception
    error_handler[response]
  end

  command
rescue Timeout::Error
  error_handler[CommandTimeout.new(command.to_s)]
ensure
  @commands.delete command
end
write_command(command) click to toggle source

@private

# File lib/adhearsion/call.rb, line 503
def write_command(command)
  abort Hangup.new(@end_reason) unless active? || command.is_a?(Punchblock::Command::Hangup)
  merge_headers command.headers if command.respond_to? :headers
  logger.debug "Executing command #{command.inspect}"
  unless command.is_a?(Punchblock::Command::Dial)
    command.target_call_id = id
    command.domain = domain
  end
  client.execute_command command
end

Private Instance Methods

client() click to toggle source
# File lib/adhearsion/call.rb, line 579
def client
  @client
end
finalize() click to toggle source
# File lib/adhearsion/call.rb, line 593
def finalize
  ::Logging::Repository.instance.delete logger_id
end
merge_headers(headers) click to toggle source
# File lib/adhearsion/call.rb, line 587
def merge_headers(headers)
  headers.each do |name, value|
    variables[name.to_s.downcase.gsub('-', '_')] = value
  end
end
offer() click to toggle source
# File lib/adhearsion/call.rb, line 575
def offer
  @offer
end
transport() click to toggle source
# File lib/adhearsion/call.rb, line 583
def transport
  offer.transport if offer
end