diff --git a/CHANGELOG.md b/CHANGELOG.md index 734c187..af99195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## [7.32.0](https://github.com/plivo/plivo-go/tree/v7.32.0) (2023-06-28) +**Audio Streaming** +- API support for starting, deleting, getting streams on a live call +- XML creation support for stream element + ## [7.31.0](https://github.com/plivo/plivo-go/tree/v7.31.0) (2023-06-02) **Feature - CNAM Lookup** - Added New Param `cnam_lookup` in to the response of the [list all numbers API], [list single number API] diff --git a/baseclient.go b/baseclient.go index 3223289..97a1c54 100644 --- a/baseclient.go +++ b/baseclient.go @@ -13,7 +13,7 @@ import ( "github.com/google/go-querystring/query" ) -const sdkVersion = "7.31.0" +const sdkVersion = "7.32.0" const lookupBaseUrl = "lookup.plivo.com" diff --git a/calls.go b/calls.go index 6f89ca0..3f87612 100644 --- a/calls.go +++ b/calls.go @@ -181,6 +181,12 @@ type CallRecordResponse struct { RecordingID string `json:"recording_id,omitempty" url:"recording_id,omitempty"` } +type CallStreamResponse struct { + Message string `json:"message,omitempty" url:"message,omitempty"` + APIID string `json:"api_id,omitempty" url:"api_id,omitempty"` + StreamID string `json:"stream_id,omitempty" url:"stream_id,omitempty"` +} + type CallPlayParams struct { URLs string `json:"urls" url:"urls"` Length string `json:"length,omitempty" url:"length,omitempty"` @@ -218,6 +224,60 @@ type CallDTMFResponseBody struct { ApiID string `json:"api_id,omitempty" url:"api_id,omitempty"` } +type CallStreamParams struct { + ServiceUrl string `json:"service_url,omitempty" url:"service_url,omitempty"` + Bidirectional bool `json:"bidirectional,omitempty" url:"bidirectional,omitempty"` + AudioTrack string `json:"audio_track,omitempty" url:"audio_track,omitempty"` + StreamTimeout int64 `json:"stream_timeout,omitempty" url:"stream_timeout,omitempty"` + StatusCallbackUrl string `json:"status_callback_url,omitempty" url:"status_callback_url,omitempty"` + StatusCallbackMethod string `json:"status_callback_method,omitempty" url:"status_callback_method,omitempty"` + ContentType string `json:"content_type,omitempty" url:"content_type,omitempty"` + ExtraHeaders string `json:"extra_headers,omitempty" url:"extra_headers,omitempty"` +} + +type CallStreamGetAllObject struct { + AudioTrack string `json:"audio_track" url:"audio_track"` + Bidirectional bool `json:"bidirectional" url:"bidirectional"` + BilledAmount string `json:"billed_amount" url:"billed_amount"` + BillDuration int64 `json:"bill_duration" url:"bill_duration"` + CallUUID string `json:"call_uuid" url:"call_uuid"` + CreatedAt string `json:"created_at" url:"created_at"` + EndTime string `json:"end_time" url:"end_time"` + PlivoAuthId string `json:"plivo_auth_id" url:"plivo_auth_id"` + ResourceURI string `json:"resource_uri" url:"resource_uri"` + RoundedBillDuration string `json:"rounded_bill_duration" url:"rounded_bill_duration"` + ServiceURL string `json:"service_url" url:"service_url"` + StartTime string `json:"start_time" url:"start_time"` + Status string `json:"status" url:"status"` + StatusCallbackURL string `json:"status_callback_url" url:"status_callback_url"` + StreamID string `json:"stream_id" url:"stream_id"` +} + +type CallStreamGetAll struct { + ApiID string `json:"api_id,omitempty" url:"api_id,omitempty"` + Meta Meta `json:"meta,omitempty" url:"meta,omitempty"` + Objects []CallStreamGetAllObject `json:"objects" url:"objects"` +} + +type CallStreamGetSpecific struct { + ApiID string `json:"api_id,omitempty" url:"api_id,omitempty"` + AudioTrack string `json:"audio_track" url:"audio_track"` + Bidirectional bool `json:"bidirectional" url:"bidirectional"` + BilledAmount string `json:"billed_amount" url:"billed_amount"` + BillDuration int64 `json:"bill_duration" url:"bill_duration"` + CallUUID string `json:"call_uuid" url:"call_uuid"` + CreatedAt string `json:"created_at" url:"created_at"` + EndTime string `json:"end_time" url:"end_time"` + PlivoAuthId string `json:"plivo_auth_id" url:"plivo_auth_id"` + ResourceURI string `json:"resource_uri" url:"resource_uri"` + RoundedBillDuration string `json:"rounded_bill_duration" url:"rounded_bill_duration"` + ServiceURL string `json:"service_url" url:"service_url"` + StartTime string `json:"start_time" url:"start_time"` + Status string `json:"status" url:"status"` + StatusCallbackURL string `json:"status_callback_url" url:"status_callback_url"` + StreamID string `json:"stream_id" url:"stream_id"` +} + func (service *CallService) List(params CallListParams) (response *CallListResponse, err error) { req, err := service.client.NewRequest("GET", params, "Call") if err != nil { @@ -337,6 +397,54 @@ func (service *CallService) StopRecording(callId string) (err error) { return } +func (service *CallService) Stream(CallId string, params CallStreamParams) (response *CallStreamResponse, err error) { + req, err := service.client.NewRequest("POST", params, "Call/%s/Stream", CallId) + if err != nil { + return + } + response = &CallStreamResponse{} + err = service.client.ExecuteRequest(req, response, isVoiceRequest()) + return +} + +func (service *CallService) StopAllStreams(CallId string) (err error) { + req, err := service.client.NewRequest("DELETE", nil, "Call/%s/Stream", CallId) + if err != nil { + return + } + err = service.client.ExecuteRequest(req, nil, isVoiceRequest()) + return +} + +func (service *CallService) StopSpecificStream(CallId string, StreamId string) (err error) { + req, err := service.client.NewRequest("DELETE", nil, "Call/%s/Stream/%s", CallId, StreamId) + if err != nil { + return + } + err = service.client.ExecuteRequest(req, nil, isVoiceRequest()) + return +} + +func (service *CallService) GetAllStreams(CallId string) (response *CallStreamGetAll, err error) { + req, err := service.client.NewRequest("GET", nil, "Call/%s/Stream", CallId) + if err != nil { + return + } + response = &CallStreamGetAll{} + err = service.client.ExecuteRequest(req, response, isVoiceRequest()) + return +} + +func (service *CallService) GetSpecificStream(CallId string, StreamId string) (response *CallStreamGetSpecific, err error) { + req, err := service.client.NewRequest("GET", nil, "Call/%s/Stream/%s", CallId, StreamId) + if err != nil { + return + } + response = &CallStreamGetSpecific{} + err = service.client.ExecuteRequest(req, response, isVoiceRequest()) + return +} + func (service *CallService) Speak(callId string, params CallSpeakParams) (response *CallSpeakResponse, err error) { req, err := service.client.NewRequest("POST", params, "Call/%s/Speak", callId) if err != nil { diff --git a/calls_test.go b/calls_test.go index e5bd6a8..d943691 100644 --- a/calls_test.go +++ b/calls_test.go @@ -306,3 +306,63 @@ func TestCallService_CancelRequest(t *testing.T) { assertRequest(t, "DELETE", "Request/%s", RequestID) } + +func TestCallService_Stream(t *testing.T) { + expectResponse("liveCallStreamCreateResponse.json", 202) + CallID := "CallId" + + if _, err := client.Calls.Stream(CallID, CallStreamParams{ServiceUrl: "test-url"}); err != nil { + panic(err) + } + + cl := client.httpClient + client.httpClient = nil + _, err := client.Calls.Stream(CallID, CallStreamParams{ServiceUrl: "test-url"}) + if err == nil { + client.httpClient = cl + panic(errors.New("error expected")) + } + client.httpClient = cl + + assertRequest(t, "POST", "Call/%s/Stream", CallID) +} + +func TestCallService_StopAllStreams(t *testing.T) { + expectResponse("", 204) + CallID := "CallId" + + if err := client.Calls.StopAllStreams(CallID); err != nil { + panic(err) + } + + cl := client.httpClient + client.httpClient = nil + err := client.Calls.StopAllStreams(CallID) + if err == nil { + client.httpClient = cl + panic(errors.New("error expected")) + } + client.httpClient = cl + + assertRequest(t, "DELETE", "Call/%s/Stream", CallID) +} + +func TestCallService_GetAllStreams(t *testing.T) { + expectResponse("liveCallStreamGetAllResponse.json", 200) + CallID := "CallId" + + if _, err := client.Calls.GetAllStreams(CallID); err != nil { + panic(err) + } + + cl := client.httpClient + client.httpClient = nil + _, err := client.Calls.GetAllStreams(CallID) + if err == nil { + client.httpClient = cl + panic(errors.New("error expected")) + } + client.httpClient = cl + + assertRequest(t, "GET", "Call/%s/Stream", CallID) +} diff --git a/fixtures/liveCallStreamCreateResponse.json b/fixtures/liveCallStreamCreateResponse.json new file mode 100644 index 0000000..d4cd239 --- /dev/null +++ b/fixtures/liveCallStreamCreateResponse.json @@ -0,0 +1,5 @@ +{ + "api_id": "ff09383e-246f-11ed-a1fe-0242ac110004", + "message": "audio streaming started", + "stream_id": "30852c23-ee79-4eb4-b7b9-cd360545965b" +} \ No newline at end of file diff --git a/fixtures/liveCallStreamGetAllResponse.json b/fixtures/liveCallStreamGetAllResponse.json new file mode 100644 index 0000000..f7bf5a3 --- /dev/null +++ b/fixtures/liveCallStreamGetAllResponse.json @@ -0,0 +1,21 @@ +{ + "api_id": "8d1d27f0-24f8-11ed-8311-0242ac11000b", + "meta": { + "count": 1, + "limit": 20, + "next": null, + "offset": 0, + "previous": null + }, + "objects": [ + { + "call_uuid": "4f045f7a-b04a-4364-8523-74e6072f4f72", + "end_time": null, + "service_url": "ws://3ee3-106-51-87-58.ngrok.io", + "start_time": null, + "status": "initiated", + "status_callback_url": "", + "stream_id": "c436abf8-e8a1-4d96-9aa8-b9143f7f3517" + } + ] +} \ No newline at end of file diff --git a/xml/plivoxml.go b/xml/plivoxml.go index af5ed0e..37ca1f8 100644 --- a/xml/plivoxml.go +++ b/xml/plivoxml.go @@ -1383,3 +1383,63 @@ func wordTitle(str string) string { lengthOfFinalString := len(finalString) return finalString[1:lengthOfFinalString] } + +type StreamElement struct { + Contents string `xml:",innerxml"` + + Bidirectional *bool `xml:"bidirectional,attr"` + + AudioTrack *string `xml:"audioTrack,attr"` + + StreamTimeout *int `xml:"streamTimeout,attr"` + + StatusCallbackUrl *string `xml:"statusCallbackUrl,attr"` + + StatusCallbackMethod *string `xml:"statusCallbackMethod,attr"` + + ContentType *string `xml:"contentType,attr"` + + ExtraHeaders *string `xml:"extraHeaders,attr"` + + XMLName xml.Name `xml:"Stream"` +} + +func (e StreamElement) SetBidirectional(value bool) StreamElement { + e.Bidirectional = &value + return e +} + +func (e StreamElement) SetAudioTrack(value string) StreamElement { + e.AudioTrack = &value + return e +} + +func (e StreamElement) SetStreamTimeout(value int) StreamElement { + e.StreamTimeout = &value + return e +} + +func (e StreamElement) SetStatusCallbackUrl(value string) StreamElement { + e.StatusCallbackUrl = &value + return e +} + +func (e StreamElement) SetStatusCallbackMethod(value string) StreamElement { + e.StatusCallbackMethod = &value + return e +} + +func (e StreamElement) SetContentType(value string) StreamElement { + e.ContentType = &value + return e +} + +func (e StreamElement) SetExtraHeaders(value string) StreamElement { + e.ExtraHeaders = &value + return e +} + +func (e StreamElement) SetContents(value string) StreamElement { + e.Contents = value + return e +} diff --git a/xml/plivoxml_test.go b/xml/plivoxml_test.go index 0fcfc18..b1597c8 100644 --- a/xml/plivoxml_test.go +++ b/xml/plivoxml_test.go @@ -78,3 +78,11 @@ func TestMPCXMLWithHold(t *testing.T) { }, }.String()) } + +func TestStreamXML(t *testing.T) { + assert.Equal(t, "wss://test.url", ResponseElement{ + Contents: []interface{}{ + new(StreamElement).SetBidirectional(true).SetContents("wss://test.url").SetExtraHeaders("a=1,b=2"), + }, + }.String()) +}