-
Notifications
You must be signed in to change notification settings - Fork 16
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
Allow request.Audio reader be streaming and minor API changes #2
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like where this is going. I have a few concerns about the blockingReader
type, and I think there's actually standard library way of solving it. See my comments in client.go.
client.go
Outdated
@@ -67,7 +119,7 @@ func (c *Client) CreateDownchannel(accessToken string) (<-chan *Message, error) | |||
|
|||
// Do posts a request to the AVS service's /events endpoint. | |||
func (c *Client) Do(request *Request) (*Response, error) { | |||
body := &bytes.Buffer{} | |||
body := NewBlockingReader() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The blockingReader
type has quite a bit going on inside of it for the purposes of avoiding bytes.Buffer
's EOF on empty. Do you think io.Pipe()
would work here? It's used for purposes like these where you need an intermediate in-memory buffer.
Here's a quick example:
out, in := io.Pipe()
writer := multipart.NewWriter(in)
// ...
// Send the request to AVS.
req, err := http.NewRequest("POST", EventsURL, out)
Inside the goroutine I think you need to call out.Close()
when done copying, or out.CloseWithError(err)
if an error occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback. I too was slightly dissatisfied with having to roll something custom for this.
I did start with using an io.Pipe()
but the problem is that io.Pipe()
is always unbuffered. To use it, the entire writing section would need to be in a goroutine to avoid a deadlock before the HTTP body read is called.
An issue similar to this was discussed here: https://groups.google.com/forum/#!topic/golang-dev/k0bSal8eDyE and a buffered pipe was incorporated into net/http2/pipe.go
but not exported.
I'm open to suggestions.
client.go
Outdated
mut *sync.Mutex | ||
eof bool | ||
err error | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty complex type for piping data. See my comment below in the Do
function about a possible alternative.
RecognizeProfileCloseTalk = RecognizeProfile("CLOSE_TALK") | ||
RecognizeProfileNearField = RecognizeProfile("NEAR_FIELD") | ||
RecognizeProfileFarField = RecognizeProfile("FAR_FIELD") | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks for adding these! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation with io.Pipe
worked out pretty cleanly. I can squash these commits if you're happy with it this way.
I think this looks great! You can go ahead and squash if you want. I added two comments but they're not super important to address. |
I addressed your two comments and squashed the latest two commits. |
Excellent, merged! |
When uploading a file, request.Audio (io.Reader) is used in an io.Copy to copy to the audio part of the upload. This works fine when request.Audio is a file but when request.Audio is a data stream (recording live) the HTTP request reads from the Body (a byte.Buffer) very quickly with a large buffer size and after 1 or 2 reads, gets an io.EOF because no more recorded data has been added to the buffer and finishes the request prematurely.
The implementation uses a blockingBuffer which returns the data in the buffer if any exists and blocks if the body is not finished (EOF) and there is no data. The blocking behavior in this case is desired to prevent the HTTP request from reading until more Audio data is written to the buffer.
The additional benefit of not blocking on the io.Copy is that the request can begin and (in NEAR_FIELD and FAR_FIELD profiles), AVS can begin processing the data and even return a directive before all of the audio is POSTed.
Additionally, two API changes were made.
https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechsynthesizer#speak
https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechrecognizer#profiles