lib/adhearsion/voip/asterisk/commands.rb in adhearsion-1.1.0 vs lib/adhearsion/voip/asterisk/commands.rb in adhearsion-1.1.1

- old
+ new

@@ -40,10 +40,12 @@ variable "TRANSFER_CONTEXT" => options[:context] if options && options.has_key?(:context) extend_dynamic_features_with 'blindxfer' end } unless defined? DYNAMIC_FEATURE_EXTENSIONS + PLAYBACK_SUCCESS = 'SUCCESS' unless defined? PLAYBACK_SUCCESS + # Utility method to write to pbx. # @param [String] message raw message def write(message) to_pbx.print(message + "\n") end @@ -195,18 +197,37 @@ # @example Play sound file, speak number, play two more sound files # play %w"a-connect-charge-of 22 cents-per-minute will-apply" # @example Play two sound files # play "you-sound-cute", "what-are-you-wearing" # + # @return [Boolean] true is returned if everything was successful. Otherwise, false indicates that + # some sound file(s) could not be played. def play(*arguments) + result = true unless play_time(arguments) arguments.flatten.each do |argument| - play_numeric(argument) || play_string(argument) + # result starts off as true. But if the following command ever returns false, then result + # remains false. + result &= play_numeric(argument) || play_string(argument) end end + result end + # Same as {#play}, but immediately raises an exception if a sound file cannot be played. + # + # @return [true] + # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played + def play!(*arguments) + unless play_time(arguments) + arguments.flatten.each do |argument| + play_numeric(argument) || play_string!(argument) + end + end + true + end + # Records a sound file with the given name. If no filename is specified a file named by Asterisk # will be created and returned. Else the given filename will be returned. If a relative path is # given, the file will be saved in the default Asterisk sound directory, /var/lib/spool/asterisk # by default. # @@ -476,17 +497,37 @@ # absolute timeout. # # Note that when the digit limit is not specified the :accept_key becomes "#". # Otherwise there would be no way to end the collection of digits. You can # obviously override this by passing in a new key with :accept_key. + # + # @return [String] The keypad input received. An empty string is returned in the + # absense of input. If the :accept_key argument was pressed, it + # will not appear in the output. def input(*args) options = args.last.kind_of?(Hash) ? args.pop : {} number_of_digits = args.shift - sound_files = Array options.delete(:play) - timeout = options.delete(:timeout) - terminating_key = options.delete(:accept_key) + begin + input! number_of_digits, options + rescue PlaybackError => e + ahn_log.agi.warn { e } + retry # If sound playback fails, play the remaining sound files and wait for digits + end + end + + # Same as {#input}, but immediately raises an exception if sound playback fails + # + # @return (see #input) + # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played + def input!(*args) + options = args.last.kind_of?(Hash) ? args.pop : {} + number_of_digits = args.shift + + options[:play] = [*options[:play]].compact + timeout = options[:timeout] + terminating_key = options[:accept_key] terminating_key = if terminating_key terminating_key.to_s elsif number_of_digits.nil? && !terminating_key.equal?(false) '#' end @@ -496,11 +537,21 @@ "argument to simulate unlimited digits." if number_of_digits == -1 raise ArgumentError, "The number of digits must be positive!" end buffer = '' - key = sound_files.any? ? interruptible_play(*sound_files) || '' : wait_for_digit(timeout || -1) + if options[:play].any? + # Consume the sound files one at a time. In the event of playback failure, this + # tells us which files remain unplayed. + while file = options[:play].shift + key = interruptible_play! file + break if key + end + key ||= '' + else + key = wait_for_digit timeout || -1 + end loop do return buffer if key.nil? if terminating_key if key == terminating_key return buffer @@ -919,10 +970,27 @@ return result if result != 0.chr end nil end + # + # Same as {#interruptible_play}, but immediately raises an exception if a sound file cannot be played. + # + # @return (see #interruptible_play) + # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played + def interruptible_play!(*files) + startpos = 0 + files.flatten.each do |file| + result = stream_file_result_from response("STREAM FILE", file, "1234567890*#") + if result[:endpos].to_i <= startpos + raise Adhearsion::VoIP::PlaybackError, "The sound file could not opened to stream. The parsed response was #{result.inspect}" + end + return result[:digit] unless result[:digit] == 0.chr + end + nil + end + ## # Executes the SayPhonetic command. This command will read the text passed in # out load using the NATO phonetic alphabet. # # @param [String] Passed in as the text to read aloud @@ -1060,10 +1128,19 @@ raise ArgumentError, "Can't coerce nil into AGI response! This could be a bug!" unless response_string digit = response_string[/^#{response_prefix}(-?\d+(\.\d+)?)/,1] digit.to_i.chr if digit && digit.to_s != "-1" end + def stream_file_result_from(response_string) + raise ArgumentError, "Can't coerce nil into AGI response! This could be a bug!" unless response_string + params = {} + digit, endpos = response_string.match(/^#{response_prefix}(-?\d+) endpos=(\d+)/).values_at 1, 2 + params[:digit] = digit.to_i.chr if digit && digit.to_s != "-1" + params[:endpos] = endpos.to_i if endpos + params + end + def extract_input_from(result) return false if error?(result) # return false if input_timed_out?(result) # This regexp doesn't match if there was a timeout with no @@ -1089,9 +1166,22 @@ end end def play_string(argument) execute(:playback, argument) + get_variable('PLAYBACKSTATUS') == PLAYBACK_SUCCESS + end + + # Like play_string(), but this will raise Exceptions if there's a problem. + # + # @return [true] + # @raise [Adhearsion::VoIP::PlaybackError] If a sound file cannot be played + # @see http://www.voip-info.org/wiki/view/Asterisk+cmd+Playback More information on the Asterisk Playback command + def play_string!(argument) + response = execute :playback, argument + playback = get_variable 'PLAYBACKSTATUS' + return true if playback == PLAYBACK_SUCCESS + raise PlaybackError, "Playback failed with PLAYBACKSTATUS: #{playback.inspect}. The raw response was #{response.inspect}." end def play_sound_files_for_menu(menu_instance, sound_files) digit = nil if sound_files.any? && menu_instance.digit_buffer_empty?