Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VT-4683 (Audio Streaming) #192

Merged
merged 17 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## [4.46.0](https://github.com/plivo/plivo-go/tree/v4.46.0) (2023-06-28)
**Feature - Audio Streaming**
- Added functionality to start, stop and fetch audio streams
- Added functionality to create stream XML

## [4.45.0](https://github.com/plivo/plivo-ruby/tree/v4.45.0) (2023-05-02)
**Feature - CNAM Lookup**
- Added New Param `cnam_lookup` in to the response of the [list all numbers API], [list single number API]
Expand Down Expand Up @@ -61,7 +66,6 @@
**Adding new attribute - 'message_expiry' in Send Message API**
- Added new attribute - message_expiry in Send Message API


## [4.34.0](https://github.com/plivo/plivo-ruby/tree/v4.34.0) (2022-12-16)
**10DLC: Update Campaign API**
- Added Update Campaign API
Expand All @@ -79,7 +83,6 @@
-Added 3 new keys to AccountPhoneNumber object:`tendlc_registration_status`, `tendlc_campaign_id` and `toll_free_sms_verification` (https://www.plivo.com/docs/numbers/api/account-phone-number#the-accountphonenumber-object)
-Added 3 new filters to AccountPhoneNumber - list all my numbers API:`tendlc_registration_status`, `tendlc_campaign_id` and `toll_free_sms_verification` (https://www.plivo.com/docs/numbers/api/account-phone-number#list-all-my-numbers)


## [4.30.2](https://github.com/plivo/plivo-ruby/tree/v4.30.2) (2022-09-28)
**10DLC: Campaign request**
- Added more attributes to create campaign request
Expand All @@ -88,7 +91,6 @@
**stability - faraday upgrade**
- faraday version upgrade


## [4.30.0](https://github.com/plivo/plivo-ruby/tree/v4.30.0) (2022-08-26)
**Feature - 10DLC APIs**
- Added new 10DLC APIs
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The Plivo Ruby SDK makes it simpler to integrate communications into your Ruby a
Add this line to your application's Gemfile:

```ruby
gem 'plivo', '>= 4.45.0'
gem 'plivo', '>= 4.46.0'
```

And then execute:
Expand Down
123 changes: 123 additions & 0 deletions lib/plivo/resources/calls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,73 @@ def cancel_request
@_client.send_request(resource_path, 'DELETE', nil, nil, false , is_voice_request: @_is_voice_request)
end

def start_stream(service_url, options = nil)
valid_param?(:service_url, service_url, [String, Symbol], true)
if options.nil?
return perform_action('Stream', 'POST', { service_url: service_url }, true)
end

valid_param?(:options, options, Hash, true)

params = { service_url: service_url }

if options.key?(:bidirectional) &&
valid_param?(:bidirectional, options[:bidirectional], [TrueClass, FalseClass], false )
params[:bidirectional] = options[:bidirectional]
end

if options.key?(:audio_track) &&
valid_param?(:audio_track, options[:audio_track],
[String, Symbol], false, %w[inbound outbound both])
params[:audio_track] = options[:audio_track]
end

if options.key?(:stream_timeout) &&
valid_param?(:stream_timeout, options[:stream_timeout], Integer, false)
params[:stream_timeout] = options[:stream_timeout]
end

if options.key?(:status_callback_url) &&
valid_param?(:status_callback_url, options[:status_callback_url], [String, Symbol], false)
params[:status_callback_url] = options[:status_callback_url]
end

if options.key?(:status_callback_method) &&
valid_param?(:status_callback_method, options[:status_callback_method],
[String, Symbol], false, %w[GET POST get post])
params[:status_callback_method] = options[:status_callback_method]
end

if options.key?(:content_type) &&
valid_param?(:content_type, options[:content_type], [String, Symbol, Integer], false)
params[:content_type] = options[:content_type]
end

if options.key?(:extra_headers) &&
valid_param?(:extra_headers, options[:extra_headers], [String], false)
params[:extra_headers] = options[:extra_headers]
end
perform_action('Stream', 'POST', params, true)
end

def stop_all_streams
perform_action('Stream', 'DELETE', nil, false)
end

def stop_stream(stream_id)
valid_param?(:stream_id, stream_id, [String, Symbol, Integer], true)
perform_action('Stream/' + stream_id, 'DELETE', nil, false)
end

def get_all_streams
perform_action('Stream', 'GET', nil, true )
end

def get_stream(stream_id)
valid_param?(:stream_id, stream_id, [String, Symbol, Integer], true)
perform_action('Stream/' + stream_id, 'GET', nil, true)
end

def to_s
call_details = {
answer_time: @answer_time,
Expand Down Expand Up @@ -577,6 +644,62 @@ def cancel_request(call_uuid)
valid_param?(:call_uuid, call_uuid, [String, Symbol], true)
Call.new(@_client, resource_id: call_uuid).cancel_request
end

# @param [String] service_url
# @param [Hash] options
# @option options [Boolean] :bidirectional
# @option options [String] :audio_track
# @option options [Int] :stream_timeout
# @option options [String] :status_callback_url
# @option options [String] :status_callback_method
# @option options [String] :content_type
# @option options [String] :extra_headers
def start_stream(call_uuid, service_url, options = {})
valid_param?(:call_uuid, call_uuid, [String, Symbol], true)
response = Call.new(@_client, resource_id: call_uuid).start_stream(service_url, options)
return Base::Response.new(Hash["api_id" => response.api_id,
"stream_id" => response.stream_id,
"message" => response.message])
end

def stop_all_streams(call_uuid)
valid_param?(:call_uuid, call_uuid, [String, Symbol], true )
Call.new(@_client, resource_id: call_uuid).stop_all_streams
end

def stop_stream(call_uuid, stream_id)
valid_param?(:call_uuid, call_uuid, [String, Symbol], true )
Call.new(@_client, resource_id: call_uuid).stop_stream(stream_id)
end

def get_all_streams(call_uuid)
valid_param?(:call_uuid, call_uuid, [String, Symbol], true )
response = Call.new(@_client, resource_id: call_uuid).get_all_streams
return Base::Response.new(Hash["api_id" => response.api_id,
"meta" => response.meta,
"objects" => response.objects])
end

def get_stream(call_uuid, stream_id)
valid_param?(:call_uuid, call_uuid, [String, Symbol], true )
response = Call.new(@_client, resource_id: call_uuid).get_stream(stream_id)
return Base::Response.new(Hash["api_id" => response.instance_variable_get(:@api_id),
"audio_track" => response.instance_variable_get(:@audio_track),
"bidirectional" => response.instance_variable_get(:@bidirectional),
"bill_duration" => response.instance_variable_get(:@bill_duration),
"billed_amount" => response.instance_variable_get(:@billed_amount),
"call_uuid" => response.instance_variable_get(:@call_uuid),
"created_at" => response.instance_variable_get(:@created_at),
"end_time" => response.instance_variable_get(:@end_time),
"plivo_auth_id" => response.instance_variable_get(:@plivo_auth_id),
"resource_uri" => response.instance_variable_get(:@resource_uri),
"rounded_bill_duration" => response.instance_variable_get(:@rounded_bill_duration),
"service_url" => response.instance_variable_get(:@service_url),
"start_time" => response.instance_variable_get(:@start_time),
"status" => response.instance_variable_get(:@status),
"status_callback_url" => response.instance_variable_get(:@status_callback_url),
"stream_id" => response.instance_variable_get(:@stream_id)])
end
end
end
end
2 changes: 1 addition & 1 deletion lib/plivo/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Plivo
VERSION = "4.45.0".freeze
VERSION = "4.46.0".freeze
end
1 change: 1 addition & 0 deletions lib/plivo/xml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
require_relative 'xml/plivo_xml'
require_relative 'xml/multipartycall'
require_relative 'xml/cont'
require_relative 'xml/stream'
include Plivo::Exceptions

module Plivo
Expand Down
4 changes: 2 additions & 2 deletions lib/plivo/xml/plivo_xml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ def initialize(response = nil)
end

def to_xml
'<?xml version="1.0" encoding="utf-8" ?>' + @response.to_xml
'<?xml version="1.0" encoding="utf-8" ?>' + @response.to_xml.gsub("&quot;", "\"")
end

def to_s
'<?xml version="1.0" encoding="utf-8" ?>' + @response.to_s
'<?xml version="1.0" encoding="utf-8" ?>' + @response.to_s.gsub("&quot;", "\"")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/plivo/xml/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Plivo
module XML
class Response < Element
@nestables = %w[Speak Play GetDigits GetInput Record Dial Message
Redirect Wait Hangup PreAnswer Conference DTMF MultiPartyCall]
Redirect Wait Hangup PreAnswer Conference DTMF MultiPartyCall Stream]
@valid_attributes = []

def initialize
Expand Down
27 changes: 27 additions & 0 deletions lib/plivo/xml/stream.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Plivo
module XML
class Stream < Element
@nestables = []
@valid_attributes = %w[bidirectional audioTrack streamTimeout statusCallbackUrl
statusCallbackMethod contentType extraHeaders]

SUPPORTED_BIDIRECTIONAL=%w(true false)
SUPPORTED_AUDIOTRACK=%w(inbound outbound both)
SUPPORTED_CALLBACKMETHOD=%w(GET POST)

def initialize(body, attributes = {})
if attributes[:bidirectional] && !SUPPORTED_BIDIRECTIONAL.include?(attributes[:bidirectional])
raise PlivoXMLError, "<Stream> bidirectional #{attributes[:bidirectional]} is not valid."
end
if attributes[:audioTrack] && !SUPPORTED_AUDIOTRACK.include?(attributes[:audioTrack])
raise PlivoXMLError, "<Stream> audioTrack #{attributes[:audioTrack]} is not valid."
end
if attributes[:statusCallbackMethod] && !SUPPORTED_CALLBACKMETHOD.include?(attributes[:statusCallbackMethod].upcase)
raise PlivoXMLError, "<Stream> statusCallbackMethod #{attributes[:statusCallbackMethod]} is not valid."
end
raise PlivoXMLError, 'No text set for Stream' unless body
super(body, attributes)
end
end
end
end
5 changes: 5 additions & 0 deletions spec/mocks/streamStartCreateResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"message": "stream started",
"api_id": "69487be8-3ef4-11e7-a51d-0245fa790d9e",
"stream_id": "69487be8-3ef4-11e7-a51d-0245fa790d77"
}
4 changes: 4 additions & 0 deletions spec/mocks/streamStartCreateResponses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"message": "stream started",
"api_id": "69487be8-3ef4-11e7-a51d-0245fa790d9e"
}
16 changes: 16 additions & 0 deletions spec/resource_calls_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,20 @@ def to_json_list_live(list_object)
method: 'DELETE',
data: nil)
end

it 'start stream' do
id = 'MAXXXXXXXXXXXXXXXXXX'
contents = File.read(Dir.pwd + '/spec/mocks/streamStartCreateResponse.json')
response = File.read(Dir.pwd + '/spec/mocks/streamStartCreateResponses.json')
mock(202, JSON.parse(contents))
expect(JSON.parse(to_json_update(@api.calls
.start_stream(id,
'wss://mystream.ngrok.io/audiostream'))))
.to eql(JSON.parse(response))
compare_requests(uri: '/v1/Account/MAXXXXXXXXXXXXXXXXXX/Call/' + id + '/Stream/',
method: 'POST',
data: {
service_url: 'wss://mystream.ngrok.io/audiostream'
})
end
end
9 changes: 8 additions & 1 deletion spec/xml_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,15 @@
statusCallbackEvents: 'participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes'
)

resp.addStream('Starting a new stream.')
resp.addStream('Starting a new stream with params.',
{
bidirectional: 'true',
audioTrack: 'outbound'
})

xml = Plivo::XML::PlivoXML.new(resp)
puts xml.to_xml
expect(xml.to_xml).to eql("<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response><Conference callbackMethod='POST' callbackUrl='http://foo.com/confevents/' digitsMatch='#0,90,000'>My room</Conference><Dial confirmKey='3' confirmSound='http://foo.com/sound/'><Number sendDigits='wwww2410'>18217654321</Number><User sendDigits='wwww2410'>sip:[email protected]</User></Dial><Dial action='http://foo.com/dial_action/' timeout='20'><Number>18217654321</Number></Dial><Dial><Number>15671234567</Number></Dial><DTMF>12345</DTMF><GetDigits action='http://ww.foo.com/gather_pin/' method='POST'><Speak>Enter PIN number.</Speak></GetDigits><Speak>Input not recieved.</Speak><GetInput action='http://ww.foo.com/gather_feedback/' method='POST'><Speak>Tell us more about your experience.</Speak></GetInput><Speak>Statement not recieved.</Speak><Hangup reason='rejected' schedule='60'/><Speak loop='0'>Call will hangup after a min.</Speak><Message callbackMethod='POST' callbackUrl='http://foo.com/sms_status/' dst='15671234567' src='12023222222' type='sms'>Hi, message from Plivo.</Message><Play>https://amazonaws.com/Trumpet.mp3</Play><PreAnswer><Speak>This call will cost $2 a min.</Speak></PreAnswer><Speak>Thanks for dropping by.</Speak><Record action='http://foo.com/get_recording/' redirect='false' startOnDialAnswer='true'/><Dial><Number>15551234567</Number></Dial><Speak>Leave message after the beep.</Speak><Record action='http://foo.com/get_recording/' finishOnKey='*' maxLength='30'/><Speak>Recording not received.</Speak><Speak>Your call is being transferred.</Speak><Redirect>http://foo.com/redirect/</Redirect><Speak loop='3'>Go green, go plivo.</Speak><Speak>I will wait 7 seconds starting now!</Speak><Wait length='7'/><Speak>I just waited 7 seconds.</Speak><Wait beep='true' length='120'/><Play>https://s3.amazonaws.com/abc.mp3</Play><Wait length='10'/><Speak>Hello</Speak><Wait length='10' minSilence='3000' silence='true'/><Speak>Hello, welcome to the Jungle.</Speak><MultiPartyCall agentHoldMusicMethod='GET' coachMode='true' customerHoldMusicMethod='GET' endMpcOnExit='false' enterSound='beep:1' enterSoundMethod='GET' exitSound='beep:2' exitSoundMethod='GET' hold='false' maxDuration='1000' maxParticipants='10' mute='false' onExitActionMethod='POST' record='false' recordFileFormat='mp3' recordMinMemberCount='1' recordingCallbackMethod='GET' relayDTMFInputs='false' role='Agent' startMpcOnEnter='true' startRecordingAudioMethod='GET' statusCallbackEvents='participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes' statusCallbackMethod='POST' stayAlone='false' stopRecordingAudioMethod='GET' waitMusicMethod='GET'>Nairobi</MultiPartyCall></Response>")
expect(xml.to_xml).to eql("<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response><Conference callbackMethod='POST' callbackUrl='http://foo.com/confevents/' digitsMatch='#0,90,000'>My room</Conference><Dial confirmKey='3' confirmSound='http://foo.com/sound/'><Number sendDigits='wwww2410'>18217654321</Number><User sendDigits='wwww2410'>sip:[email protected]</User></Dial><Dial action='http://foo.com/dial_action/' timeout='20'><Number>18217654321</Number></Dial><Dial><Number>15671234567</Number></Dial><DTMF>12345</DTMF><GetDigits action='http://ww.foo.com/gather_pin/' method='POST'><Speak>Enter PIN number.</Speak></GetDigits><Speak>Input not recieved.</Speak><GetInput action='http://ww.foo.com/gather_feedback/' method='POST'><Speak>Tell us more about your experience.</Speak></GetInput><Speak>Statement not recieved.</Speak><Hangup reason='rejected' schedule='60'/><Speak loop='0'>Call will hangup after a min.</Speak><Message callbackMethod='POST' callbackUrl='http://foo.com/sms_status/' dst='15671234567' src='12023222222' type='sms'>Hi, message from Plivo.</Message><Play>https://amazonaws.com/Trumpet.mp3</Play><PreAnswer><Speak>This call will cost $2 a min.</Speak></PreAnswer><Speak>Thanks for dropping by.</Speak><Record action='http://foo.com/get_recording/' redirect='false' startOnDialAnswer='true'/><Dial><Number>15551234567</Number></Dial><Speak>Leave message after the beep.</Speak><Record action='http://foo.com/get_recording/' finishOnKey='*' maxLength='30'/><Speak>Recording not received.</Speak><Speak>Your call is being transferred.</Speak><Redirect>http://foo.com/redirect/</Redirect><Speak loop='3'>Go green, go plivo.</Speak><Speak>I will wait 7 seconds starting now!</Speak><Wait length='7'/><Speak>I just waited 7 seconds.</Speak><Wait beep='true' length='120'/><Play>https://s3.amazonaws.com/abc.mp3</Play><Wait length='10'/><Speak>Hello</Speak><Wait length='10' minSilence='3000' silence='true'/><Speak>Hello, welcome to the Jungle.</Speak><MultiPartyCall agentHoldMusicMethod='GET' coachMode='true' customerHoldMusicMethod='GET' endMpcOnExit='false' enterSound='beep:1' enterSoundMethod='GET' exitSound='beep:2' exitSoundMethod='GET' hold='false' maxDuration='1000' maxParticipants='10' mute='false' onExitActionMethod='POST' record='false' recordFileFormat='mp3' recordMinMemberCount='1' recordingCallbackMethod='GET' relayDTMFInputs='false' role='Agent' startMpcOnEnter='true' startRecordingAudioMethod='GET' statusCallbackEvents='participant-speak-events, participant-digit-input-events, add-participant-api-events, participant-state-changes, mpc-state-changes' statusCallbackMethod='POST' stayAlone='false' stopRecordingAudioMethod='GET' waitMusicMethod='GET'>Nairobi</MultiPartyCall><Stream>Starting a new stream.</Stream><Stream audioTrack='outbound' bidirectional='true'>Starting a new stream with params.</Stream></Response>")
end
end