class Adhearsion::Call
Encapsulates call-related data and behavior.
Constants
- CommandTimeout
- ExpiredError
- Hangup
Attributes
@return [Integer] the number of seconds after the call is hung up that the controller will remain active
@return [true, false] whether or not the call should be automatically hung up after executing its controller
@return [Array<Adhearsion::CallController>] the set of call controllers executing on the call
@return [String] the reason code for the call ending
@return [Symbol] the reason for the call ending
@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.
@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.
@return [Hash<String => String>] a collection of SIP headers set during the call
Public Class Methods
# 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
# 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
# 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
@return [Boolean] if the call is currently active or not (disconnected)
# File lib/adhearsion/call.rb, line 319 def active? !end_reason end
# 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
@private
# File lib/adhearsion/call.rb, line 372 def clear_from_active_calls Adhearsion.active_calls.remove_inactive_call current_actor end
# File lib/adhearsion/call.rb, line 197 def commands @commands.clone end
# 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
@return [String, nil] The domain on which the call resides
# File lib/adhearsion/call.rb, line 108 def domain offer.domain if offer end
@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 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
@private
# File lib/adhearsion/call.rb, line 296 def guards_for_target(target) target ? [target_from_join_options(join_options_with_target(target))] : [] end
# 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
@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
@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
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
@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
@private
# File lib/adhearsion/call.rb, line 527 def logger_id "#{self.class}: #{id}@#{domain}" end
# File lib/adhearsion/call.rb, line 464 def mute write_and_await_response Punchblock::Command::Mute.new rescue Punchblock::ProtocolError => e abort e end
# File lib/adhearsion/call.rb, line 300 def on_end(&block) register_event_handler Punchblock::Event::End, &block end
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
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
@private
# File lib/adhearsion/call.rb, line 564 def pause_controllers controllers.each(&:pause!) end
Hash of joined peers @return [Hash<String => Adhearsion::Call>]
# File lib/adhearsion/call.rb, line 158 def peers @peers.clone end
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
@private
# File lib/adhearsion/call.rb, line 559 def register_controller(controller) @controllers << controller end
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
@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
# 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 a label
@param [String, Symbol] label
# File lib/adhearsion/call.rb, line 141 def remove_tag(label) @tags.reject! { |tag| tag == label } end
@private
# File lib/adhearsion/call.rb, line 569 def resume_controllers controllers.each(&:resume!) end
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 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
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
@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
# File lib/adhearsion/call.rb, line 304 def terminating! logger.debug "Call is terminating" @call_terminating = true end
@return [Boolean] if the call is currently terminating/terminated
# File lib/adhearsion/call.rb, line 312 def terminating? !active? || @call_terminating end
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
# File lib/adhearsion/call.rb, line 470 def unmute write_and_await_response Punchblock::Command::Unmute.new rescue Punchblock::ProtocolError => e abort e end
@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 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
# 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
# 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
@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
@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
# File lib/adhearsion/call.rb, line 579 def client @client end
# File lib/adhearsion/call.rb, line 593 def finalize ::Logging::Repository.instance.delete logger_id end
# 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
# File lib/adhearsion/call.rb, line 575 def offer @offer end
# File lib/adhearsion/call.rb, line 583 def transport offer.transport if offer end