Skip to content

Commit

Permalink
VT-4683 : Audio streaming implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
prashantp-plivo committed Sep 27, 2022
1 parent dc54d95 commit 65e64f0
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## [4.31.0](https://github.com/plivo/plivo-go/tree/v4.31.0) (2022-09-29)
**Feature - Audio Streaming**
- Added functionality to start, stop and fetch audio streams

## [4.30.0](https://github.com/plivo/plivo-go/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.30.0'
gem 'plivo', '>= 4.31.0'
```

And then execute:
Expand Down
115 changes: 115 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, Symbol, Integer], 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 @@ -575,6 +642,54 @@ 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.api_id,
"call_uuid" => response.call_uuid,
"end_time" => response.end_time,
"service_url" => response.service_url,
"start_time" => response.start_time,
"status" => response.status,
"status_callback_url" => response.status_callback_url,
"stream_id" => response.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.30.0".freeze
VERSION = "4.31.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
41 changes: 41 additions & 0 deletions lib/plivo/xml/stream.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 && attributes[:extraHeaders]
input = attributes[:extraHeaders]
headers = Hash.new
input.each do |keys, value|
if keys.to_s.end_with?("X-PH")
headers[keys.to_s] = value
else
headers[keys.to_s + "X-PH"] = value
end
end
headersString = headers.to_json
puts headersString
attributes[:extraHeaders] = headersString
end
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
4 changes: 4 additions & 0 deletions spec/mocks/streamStartCreateResponse.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"
}
15 changes: 15 additions & 0 deletions spec/resource_calls_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,19 @@ 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')
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(contents))
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

0 comments on commit 65e64f0

Please sign in to comment.