adhearsion 2.1.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG.md +87 -0
  2. data/Guardfile +1 -1
  3. data/Rakefile +1 -2
  4. data/adhearsion.gemspec +1 -1
  5. data/features/cli_create.feature +1 -0
  6. data/features/step_definitions/cli_steps.rb +0 -10
  7. data/lib/adhearsion.rb +11 -0
  8. data/lib/adhearsion/call.rb +27 -12
  9. data/lib/adhearsion/call_controller.rb +1 -1
  10. data/lib/adhearsion/call_controller/dial.rb +3 -1
  11. data/lib/adhearsion/call_controller/input.rb +4 -4
  12. data/lib/adhearsion/call_controller/menu_dsl.rb +1 -0
  13. data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +26 -0
  14. data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +2 -14
  15. data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +6 -6
  16. data/lib/adhearsion/call_controller/output.rb +20 -14
  17. data/lib/adhearsion/call_controller/output/abstract_player.rb +5 -5
  18. data/lib/adhearsion/call_controller/output/formatter.rb +68 -68
  19. data/lib/adhearsion/call_controller/record.rb +91 -28
  20. data/lib/adhearsion/call_controller/utility.rb +12 -5
  21. data/lib/adhearsion/calls.rb +1 -1
  22. data/lib/adhearsion/configuration.rb +4 -0
  23. data/lib/adhearsion/events.rb +2 -1
  24. data/lib/adhearsion/generators/app/app_generator.rb +2 -2
  25. data/lib/adhearsion/generators/app/templates/Gemfile.erb +4 -0
  26. data/lib/adhearsion/generators/app/templates/Rakefile +5 -0
  27. data/lib/adhearsion/generators/app/templates/lib/simon_game.rb +3 -0
  28. data/lib/adhearsion/generators/app/templates/rspec +2 -0
  29. data/lib/adhearsion/generators/app/templates/spec/call_controllers/simon_game_spec.rb +142 -0
  30. data/lib/adhearsion/generators/controller/templates/lib/controller.rb +1 -1
  31. data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +2 -0
  32. data/lib/adhearsion/initializer.rb +3 -2
  33. data/lib/adhearsion/outbound_call.rb +5 -2
  34. data/lib/adhearsion/router/route.rb +13 -2
  35. data/lib/adhearsion/rspec.rb +1 -1
  36. data/lib/adhearsion/statistics.rb +138 -0
  37. data/lib/adhearsion/tasks/environment.rb +1 -1
  38. data/lib/adhearsion/version.rb +1 -1
  39. data/spec/adhearsion/call_controller/dial_spec.rb +26 -0
  40. data/spec/adhearsion/call_controller/input_spec.rb +13 -1
  41. data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +76 -0
  42. data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +8 -6
  43. data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +4 -4
  44. data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +6 -6
  45. data/spec/adhearsion/call_controller/output/formatter_spec.rb +3 -5
  46. data/spec/adhearsion/call_controller/output_spec.rb +59 -25
  47. data/spec/adhearsion/call_controller/record_spec.rb +123 -2
  48. data/spec/adhearsion/call_controller/utility_spec.rb +31 -11
  49. data/spec/adhearsion/call_spec.rb +77 -36
  50. data/spec/adhearsion/calls_spec.rb +13 -0
  51. data/spec/adhearsion/initializer_spec.rb +7 -0
  52. data/spec/adhearsion/outbound_call_spec.rb +14 -0
  53. data/spec/adhearsion/punchblock_plugin_spec.rb +5 -2
  54. data/spec/adhearsion/router/openended_route_spec.rb +2 -1
  55. data/spec/adhearsion/router/route_spec.rb +9 -1
  56. data/spec/adhearsion/router/unaccepting_route_spec.rb +2 -1
  57. data/spec/adhearsion/statistics/dump_spec.rb +38 -0
  58. data/spec/adhearsion/statistics_spec.rb +61 -0
  59. data/spec/adhearsion_spec.rb +21 -1
  60. data/spec/spec_helper.rb +2 -0
  61. metadata +16 -7
@@ -1,5 +1,92 @@
1
1
  # [develop](https://github.com/adhearsion/adhearsion)
2
2
 
3
+ # [2.2.0](https://github.com/adhearsion/adhearsion/compare/v2.1.3...v2.2.0) - [2012-12-17](https://rubygems.org/gems/adhearsion/versions/2.2.0)
4
+ * Feature: Statistics API providing counts of calls dialed, offered, rejected, routed and active
5
+
6
+ ```ruby
7
+ Adhearsion.statistics.dump # => #<Adhearsion::Statistics::Dump timestamp=2012-12-17 10:31:05 -0500, call_counts={:dialed=>0, :offered=>18, :routed=>6, :rejected=>0, :active=>0}, calls_by_route={"Sesame Street"=>3, "Mr. Rogers Neighborhood"=>2, "default"=>1}>
8
+ ```
9
+ * Feature: Accessor for peer calls when bridged
10
+
11
+ ```ruby
12
+ call.peers # => {"0f4h382j290k09k" => #<Adhearsion::Call ...>}
13
+ ```
14
+ * Feature: Allow specifying controller metadata when originating outbound calls
15
+
16
+ ```ruby
17
+ Adhearsion::OutboundCall.originate 'foo@bar.com', controller: FooBarController, controller_metadata: {foo: 'bar'}
18
+ ```
19
+ * Feature: Allow specifying confirmation controller metadata to `CallController#dial`
20
+
21
+ ```ruby
22
+ dial 'foo@bar.com', confirm: ConfirmationController, confirm_metadata: {foo: 'bar'}
23
+ ```
24
+ * Feature: Set default voice on output components when specified in config
25
+
26
+ ```ruby
27
+ config.punchblock.default_voice = 'kal'
28
+ ```
29
+ * Feature: Be more flexible about DTMF utterance parsing
30
+ * Feature: Added specs for the SimonGame
31
+ * Feature: Allow configuring the lifetime of a call object after hangup. This makes it possible to control the number of call objects (and therefore threads) in use by Adhearsion, by expiring them earlier or later than the 30 second default (as measured from the point at which the call disconnects).
32
+
33
+ ```ruby
34
+ config.platform.after_hangup_lifetime = 10
35
+ ```
36
+ * Feature: Support collections passed to `CallController#play`
37
+ ```ruby
38
+ recordings = ['one', 'two', 'three']
39
+ play recordings
40
+ ````
41
+ * Feature: Support arrays passed to `#match` in a `CallController#menu`
42
+ ```ruby
43
+ possible_digits = [1,2,3,4]
44
+ menu 'foobar' do
45
+ match possible_digits, FooController
46
+ end
47
+ ```
48
+ * Feature: Output document formatter for a call controller is now overridable
49
+ ```ruby
50
+ # Replacement formatter designed to render all TTS extra slow
51
+ class MyFormatter < Adhearsion::CallController::Output::Formatter
52
+ def ssml_for_text(argument, options = {})
53
+ RubySpeech::SSML.draw do
54
+ prosody rate: 'x-slow' do
55
+ argument
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ class MyController < Adhearsion::CallController
62
+ def run
63
+ speak "This will be sloooooow"
64
+ end
65
+
66
+ def output_formatter
67
+ MyFormatter.new
68
+ end
69
+ end
70
+ ```
71
+ * Feature: Refactored recording functionality into a Recorder class for easier implementation of specific APIs
72
+ ```ruby
73
+ # Alternative #record implementation, returning the input component also
74
+ def record(options = {})
75
+ recorder = Recorder.new self, options
76
+
77
+ recorder.handle_record_completion do |event|
78
+ catching_standard_errors { yield event if block_given? }
79
+ end
80
+
81
+ recorder.run
82
+ [recorder.record_component, recorder.stopper_component]
83
+ end
84
+ ```
85
+ * Bugfix: Generate sane spec defaults for new apps and controllers
86
+ * Bugfix: `CallController#record` now allows partial-second timeouts
87
+ * Bugfix: Ensure calls are removed from the active collection when they terminate cleanly
88
+ * Bugfix: Plug a big memory leak
89
+
3
90
  # [2.1.3](https://github.com/adhearsion/adhearsion/compare/v2.1.2...v2.1.3) - [2012-10-11](https://rubygems.org/gems/adhearsion/versions/2.1.3)
4
91
  * Bugfix: Originating call is now answered before joining calls using `CallController#dial`
5
92
  * Bugfix: Output controller methods no longer falsely detect a string with a colon as a URI for an audio file
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  ENV['SKIP_RCOV'] = 'true'
2
2
 
3
3
  group 'rspec' do
4
- guard 'rspec', :version => 2, :cli => '--format documentation' do
4
+ guard 'rspec', :cli => '--format documentation' do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
7
  watch('spec/spec_helper.rb') { "spec/" }
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  # -*- ruby -*-
2
- ENV['RUBY_FLAGS'] = "-I#{%w(lib ext bin spec).join(File::PATH_SEPARATOR)}"
3
2
 
4
3
  require 'bundler/gem_tasks'
5
4
  require 'bundler/setup'
@@ -9,7 +8,7 @@ task :gem => :build
9
8
 
10
9
  require 'rspec/core/rake_task'
11
10
  RSpec::Core::RakeTask.new(:spec) do |t|
12
- t.ruby_opts = "-w -r./spec/capture_warnings"
11
+ # t.ruby_opts = "-w -r./spec/capture_warnings"
13
12
  end
14
13
 
15
14
  require 'ci/reporter/rake/rspec'
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_runtime_dependency 'ffi', ["~> 1.0"]
27
27
  s.add_runtime_dependency 'future-resource', ["~> 1.0"]
28
28
  s.add_runtime_dependency 'girl_friday'
29
- s.add_runtime_dependency 'has-guarded-handlers', ["~> 1.1"]
29
+ s.add_runtime_dependency 'has-guarded-handlers', ["~> 1.5"]
30
30
  s.add_runtime_dependency 'jruby-openssl' if RUBY_PLATFORM == 'java'
31
31
  s.add_runtime_dependency 'logging', ["~> 1.8"]
32
32
  s.add_runtime_dependency 'pry'
@@ -12,6 +12,7 @@ Feature: Adhearsion Ahn CLI (Create)
12
12
  | Rakefile |
13
13
  | config/adhearsion.rb |
14
14
  | config/environment.rb |
15
+ | spec/spec_helper.rb |
15
16
  And the file "config/adhearsion.rb" should contain "Adhearsion.router"
16
17
  Then the exit status should be 0
17
18
 
@@ -22,16 +22,6 @@ When /^I wait (\d+) seconds?$/ do |arg1|
22
22
  sleep arg1.to_i
23
23
  end
24
24
 
25
- # TODO: Remove after pull request is merged in cucumber.rb from Aruba
26
- When /^I wait for (?:output|stdout) to contain "([^"]*)"$/ do |expected|
27
- Timeout::timeout(exit_timeout) do
28
- loop do
29
- break if assert_partial_output_interactive(expected)
30
- sleep 0.1
31
- end
32
- end
33
- end
34
-
35
25
  Given /^that I create a valid app under "([^"]*)"$/ do |path|
36
26
  steps %Q{
37
27
  When I run `ahn create #{path}`
@@ -34,6 +34,7 @@ module Adhearsion
34
34
  autoload :OutboundCall
35
35
  autoload :Plugin
36
36
  autoload :Router
37
+ autoload :Statistics
37
38
 
38
39
  class << self
39
40
 
@@ -105,6 +106,16 @@ module Adhearsion
105
106
  end
106
107
  end
107
108
 
109
+ #
110
+ # @return [Adhearsion::Statistics] a statistics aggregator object capable of producing stats dumps
111
+ def statistics
112
+ if instance_variable_defined?(:@statistics) && @statistics.alive?
113
+ @statistics
114
+ else
115
+ @statistics = Statistics.new.tap(&:setup_event_handlers)
116
+ end
117
+ end
118
+
108
119
  def status
109
120
  Adhearsion::Process.state_name
110
121
  end
@@ -23,17 +23,10 @@ module Adhearsion
23
23
  rescue Celluloid::DeadActorError
24
24
  raise ExpiredError, "This call is expired and is no longer accessible"
25
25
  end
26
-
27
- def proxy.join(*args)
28
- Actor.call @mailbox, :join, *args
29
- end
30
26
  end
31
27
  end
32
28
 
33
- # @private
34
- attr_accessor :offer, :client, :end_reason, :commands, :controllers
35
-
36
- attr_accessor :variables
29
+ attr_reader :end_reason, :commands, :controllers, :variables
37
30
 
38
31
  delegate :[], :[]=, :to => :variables
39
32
  delegate :to, :from, :to => :offer, :allow_nil => true
@@ -41,11 +34,13 @@ module Adhearsion
41
34
  def initialize(offer = nil)
42
35
  register_initial_handlers
43
36
 
37
+ @offer = nil
44
38
  @tags = []
45
39
  @commands = CommandRegistry.new
46
40
  @variables = {}
47
41
  @controllers = []
48
42
  @end_reason = nil
43
+ @peers = {}
49
44
 
50
45
  self << offer if offer
51
46
  end
@@ -92,6 +87,14 @@ module Adhearsion
92
87
  @tags.include? label
93
88
  end
94
89
 
90
+ #
91
+ # Hash of joined peers
92
+ # @return [Hash<String => Adhearsion::Call>]
93
+ #
94
+ def peers
95
+ @peers.clone
96
+ end
97
+
95
98
  def register_event_handler(*guards, &block)
96
99
  register_handler :event, *guards, &block
97
100
  end
@@ -117,11 +120,13 @@ module Adhearsion
117
120
 
118
121
  on_joined do |event|
119
122
  target = event.call_id || event.mixer_name
123
+ @peers[target] = Adhearsion.active_calls[target]
120
124
  signal :joined, target
121
125
  end
122
126
 
123
127
  on_unjoined do |event|
124
128
  target = event.call_id || event.mixer_name
129
+ @peers.delete target
125
130
  signal :unjoined, target
126
131
  end
127
132
 
@@ -130,14 +135,13 @@ module Adhearsion
130
135
  clear_from_active_calls
131
136
  @end_reason = event.reason
132
137
  commands.terminate
133
- after(after_end_hold_time) { current_actor.terminate! }
138
+ after(Adhearsion.config.platform.after_hangup_lifetime) { current_actor.terminate! }
134
139
  throw :pass
135
140
  end
136
141
  end
137
142
 
138
- # @private
139
- def after_end_hold_time
140
- 30
143
+ def finalize
144
+ ::Logging::Repository.reset
141
145
  end
142
146
 
143
147
  ##
@@ -197,6 +201,7 @@ module Adhearsion
197
201
 
198
202
  def reject(reason = :busy, headers = nil)
199
203
  write_and_await_response Punchblock::Command::Reject.new(:reason => reason, :headers => headers)
204
+ Adhearsion::Events.trigger_immediately :call_rejected, call: current_actor, reason: reason
200
205
  end
201
206
 
202
207
  def hangup(headers = nil)
@@ -342,6 +347,16 @@ module Adhearsion
342
347
  controllers.each(&:resume!)
343
348
  end
344
349
 
350
+ private
351
+
352
+ def offer
353
+ @offer
354
+ end
355
+
356
+ def client
357
+ @client
358
+ end
359
+
345
360
  # @private
346
361
  class CommandRegistry < ThreadSafeArray
347
362
  def terminate
@@ -88,7 +88,7 @@ module Adhearsion
88
88
  catching_standard_errors do
89
89
  exec_with_callback completion_callback
90
90
  end
91
- end.tap { |t| Adhearsion::Process.important_threads << t }
91
+ end
92
92
  end
93
93
 
94
94
  def exec_with_callback(completion_callback = nil)
@@ -32,6 +32,7 @@ module Adhearsion
32
32
  # i.e. timeout after :for if no one answers the call
33
33
  #
34
34
  # @option options [CallController] :confirm the controller to execute on answered outbound calls to give an opportunity to screen the call. The calls will be joined if the outbound call is still active after this controller completes.
35
+ # @option options [Hash] :confirm_metadata Metadata to set on the confirmation controller before executing it.
35
36
  #
36
37
  # @example Make a call to the PSTN using my SIP provider for VoIP termination
37
38
  # dial "SIP/19095551001@my.sip.voip.terminator.us"
@@ -73,6 +74,7 @@ module Adhearsion
73
74
  @options[:timeout] ||= _for if _for
74
75
 
75
76
  @confirmation_controller = @options.delete :confirm
77
+ @confirmation_metadata = @options.delete :confirm_metadata
76
78
  end
77
79
 
78
80
  def run
@@ -112,7 +114,7 @@ module Adhearsion
112
114
 
113
115
  if @confirmation_controller
114
116
  status.unconfirmed!
115
- new_call.execute_controller @confirmation_controller.new(new_call), lambda { |call| call.signal :confirmed }
117
+ new_call.execute_controller @confirmation_controller.new(new_call, @confirmation_metadata), lambda { |call| call.signal :confirmed }
116
118
  new_call.wait :confirmed
117
119
  end
118
120
 
@@ -79,7 +79,7 @@ module Adhearsion
79
79
  # invalid input, retries and timeouts, and final failures.
80
80
  #
81
81
  # @example A complete example of the method is as follows:
82
- # ask "Welcome, ", "/opt/sounds/menu-prompt.mp3", :tries => 2, :timeout => 10 do
82
+ # menu "Welcome, ", "/opt/sounds/menu-prompt.mp3", :tries => 2, :timeout => 10 do
83
83
  # match 1, OperatorController
84
84
  #
85
85
  # match 10..19 do
@@ -114,7 +114,7 @@ module Adhearsion
114
114
  #
115
115
  # #validator runs its block on each digit being collected. If it returns true, the collection is terminated.
116
116
  #
117
- # Execution of the current context resumes after #ask finishes. If you wish to jump to an entirely different controller, use #pass.
117
+ # Execution of the current context resumes after #menu finishes. If you wish to jump to an entirely different controller, use #pass.
118
118
  # Menu will return :failed if failure was reached, or :done if a match was executed.
119
119
  #
120
120
  # @param [Object] args A list of outputs to play, as accepted by #play
@@ -215,8 +215,8 @@ module Adhearsion
215
215
  }
216
216
 
217
217
  reason = input_component.complete_event.reason
218
- result = reason.respond_to?(:interpretation) ? reason.interpretation : nil
219
- parse_single_dtmf result
218
+ result = reason.respond_to?(:utterance) ? reason.utterance : nil
219
+ parse_dtmf result
220
220
  end
221
221
 
222
222
  # @private
@@ -13,6 +13,7 @@ module Adhearsion
13
13
  autoload :FixnumMatchCalculator
14
14
  autoload :RangeMatchCalculator
15
15
  autoload :StringMatchCalculator
16
+ autoload :ArrayMatchCalculator
16
17
  autoload :MenuBuilder
17
18
  autoload :Menu
18
19
  end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module Adhearsion
4
+ class CallController
5
+ module MenuDSL
6
+ class ArrayMatchCalculator < MatchCalculator
7
+ def match(query)
8
+ args = { :query => query, :exact_matches => [], :potential_matches => [] }
9
+
10
+ pattern.compact.each do |pat|
11
+ pattern_string = pat.to_s
12
+ query_string = query.to_s
13
+
14
+ if pattern_string == query_string
15
+ args[:exact_matches] << pat
16
+ elsif pattern_string.starts_with? query_string
17
+ args[:potential_matches] << pat
18
+ end
19
+ end
20
+
21
+ new_calculated_match args
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -3,20 +3,8 @@
3
3
  module Adhearsion
4
4
  class CallController
5
5
  module MenuDSL
6
- class FixnumMatchCalculator < MatchCalculator
7
-
8
- def match(query)
9
- numeric_query = coerce_to_numeric query
10
- exact_match, potential_match = nil
11
- if pattern == numeric_query
12
- exact_match = pattern
13
- elsif pattern.to_s.starts_with? query.to_s
14
- potential_match = pattern
15
- end
16
- new_calculated_match :query => query, :exact_matches => exact_match, :potential_matches => potential_match
17
- end
18
-
19
- end # class FixnumMatchCalculator
6
+ class FixnumMatchCalculator < StringMatchCalculator
7
+ end
20
8
  end
21
9
  end
22
10
  end
@@ -3,23 +3,23 @@
3
3
  module Adhearsion
4
4
  class CallController
5
5
  module MenuDSL
6
-
7
6
  class StringMatchCalculator < MatchCalculator
8
7
 
9
8
  def match(query)
10
9
  args = { :query => query, :exact_matches => nil, :potential_matches => nil }
11
10
 
12
- if pattern == query.to_s
11
+ pattern_string = pattern.to_s
12
+ query_string = query.to_s
13
+
14
+ if pattern_string == query_string
13
15
  args[:exact_matches] = [pattern]
14
- elsif pattern.starts_with? query.to_s
16
+ elsif pattern_string.starts_with? query_string
15
17
  args[:potential_matches] = [pattern]
16
18
  end
17
19
 
18
20
  new_calculated_match args
19
21
  end
20
-
21
- end # class StringMatchCalculator
22
-
22
+ end
23
23
  end
24
24
  end
25
25
  end
@@ -21,7 +21,7 @@ module Adhearsion
21
21
  # @raises [PlaybackError] if the given argument could not be played
22
22
  #
23
23
  def say(text, options = {})
24
- player.play_ssml(text, options) || player.output(Formatter.ssml_for_text(text.to_s), options)
24
+ player.play_ssml(text, options) || player.output(output_formatter.ssml_for_text(text.to_s), options)
25
25
  end
26
26
  alias :speak :say
27
27
 
@@ -34,7 +34,7 @@ module Adhearsion
34
34
  # @raises [PlaybackError] if the given argument could not be played
35
35
  #
36
36
  def say!(text, options = {})
37
- async_player.play_ssml(text, options) || async_player.output(Formatter.ssml_for_text(text.to_s), options)
37
+ async_player.play_ssml(text, options) || async_player.output(output_formatter.ssml_for_text(text.to_s), options)
38
38
  end
39
39
  alias :speak! :say!
40
40
 
@@ -61,7 +61,7 @@ module Adhearsion
61
61
  # @raises [PlaybackError] if (one of) the given argument(s) could not be played
62
62
  #
63
63
  def play(*arguments)
64
- player.play_ssml Formatter.ssml_for_collection(arguments)
64
+ player.play_ssml output_formatter.ssml_for_collection(arguments)
65
65
  true
66
66
  end
67
67
 
@@ -89,7 +89,7 @@ module Adhearsion
89
89
  # @returns [Punchblock::Component::Output]
90
90
  #
91
91
  def play!(*arguments)
92
- async_player.play_ssml Formatter.ssml_for_collection(arguments)
92
+ async_player.play_ssml output_formatter.ssml_for_collection(arguments)
93
93
  end
94
94
 
95
95
  #
@@ -104,7 +104,7 @@ module Adhearsion
104
104
  # @raises [PlaybackError] if (one of) the given argument(s) could not be played
105
105
  #
106
106
  def play_audio(file, options = nil)
107
- player.play_ssml Formatter.ssml_for_audio(file, options)
107
+ player.play_ssml output_formatter.ssml_for_audio(file, options)
108
108
  true
109
109
  end
110
110
 
@@ -121,7 +121,7 @@ module Adhearsion
121
121
  # @returns [Punchblock::Component::Output]
122
122
  #
123
123
  def play_audio!(file, options = nil)
124
- async_player.play_ssml Formatter.ssml_for_audio(file, options)
124
+ async_player.play_ssml output_formatter.ssml_for_audio(file, options)
125
125
  end
126
126
 
127
127
  #
@@ -141,7 +141,7 @@ module Adhearsion
141
141
  #
142
142
  def play_time(time, options = {})
143
143
  raise ArgumentError unless [Date, Time, DateTime].include?(time.class) && options.is_a?(Hash)
144
- player.play_ssml Formatter.ssml_for_time(time, options)
144
+ player.play_ssml output_formatter.ssml_for_time(time, options)
145
145
  true
146
146
  end
147
147
 
@@ -163,7 +163,7 @@ module Adhearsion
163
163
  #
164
164
  def play_time!(time, options = {})
165
165
  raise ArgumentError unless [Date, Time, DateTime].include?(time.class) && options.is_a?(Hash)
166
- async_player.play_ssml Formatter.ssml_for_time(time, options)
166
+ async_player.play_ssml output_formatter.ssml_for_time(time, options)
167
167
  end
168
168
 
169
169
  #
@@ -177,7 +177,7 @@ module Adhearsion
177
177
  #
178
178
  def play_numeric(number)
179
179
  raise ArgumentError unless number.kind_of?(Numeric) || number =~ /^\d+$/
180
- player.play_ssml Formatter.ssml_for_numeric(number)
180
+ player.play_ssml output_formatter.ssml_for_numeric(number)
181
181
  true
182
182
  end
183
183
 
@@ -193,7 +193,7 @@ module Adhearsion
193
193
  #
194
194
  def play_numeric!(number)
195
195
  raise ArgumentError unless number.kind_of?(Numeric) || number =~ /^\d+$/
196
- async_player.play_ssml Formatter.ssml_for_numeric(number)
196
+ async_player.play_ssml output_formatter.ssml_for_numeric(number)
197
197
  end
198
198
 
199
199
  #
@@ -236,7 +236,7 @@ module Adhearsion
236
236
  :value => grammar_accept(digits)
237
237
  }
238
238
 
239
- player.output Formatter.ssml_for(argument) do |output_component|
239
+ player.output output_formatter.ssml_for(argument) do |output_component|
240
240
  stopper.register_event_handler Punchblock::Event::Complete do |event|
241
241
  output_component.stop! unless output_component.complete?
242
242
  end
@@ -245,9 +245,8 @@ module Adhearsion
245
245
 
246
246
  stopper.stop! if stopper.executing?
247
247
  reason = stopper.complete_event.reason
248
- result = reason.interpretation if reason.respond_to? :interpretation
249
- return parse_single_dtmf result unless result.nil?
250
- result
248
+ result = reason.respond_to?(:utterance) ? reason.utterance : nil
249
+ parse_dtmf result
251
250
  end
252
251
 
253
252
  # @private
@@ -259,6 +258,13 @@ module Adhearsion
259
258
  def async_player
260
259
  @async_player ||= AsyncPlayer.new(self)
261
260
  end
261
+
262
+ #
263
+ # @return [Formatter] an output formatter for the preparation of SSML documents for submission to the engine
264
+ #
265
+ def output_formatter
266
+ Formatter.new
267
+ end
262
268
  end # Output
263
269
  end # CallController
264
270
  end # Adhearsion