Amazon Polly Service with Zend Framework

Published on Dec 4, 2016

Amazon Polly Service with Zend Framework

How to integrate Amazon Polly Text to Speech service into my Zend Framework project.


This is how Amazon introduced the brand new Polly Service:

"Amazon Polly is a service that turns text into lifelike speech. Polly lets you create applications that talk, enabling you to build entirely new categories of speech-enabled products. Polly is an Amazon AI service that uses advanced deep learning technologies to synthesize speech that sounds like a human voice. Polly includes 47 lifelike voices spread across 24 languages, so you can select the ideal voice and build speech-enabled applications that work in many different countries."

So Polly will help me to sound my application to say "Welcome, Suat!" after a succesfull login, fantastic!

I followed the steps in the "Amazon Polly Developer Guide" at Developers page and signed up for an AWS account and created an IAM user to be used with my service calls. By creating the IAM user, I have credentials consist of Access key ID and Secret access key that I will use to authenticate my connection to the AWS.

Now I need to install AWS SDK. In the project root folder, I run the following composer command:


[smozgur@local Skeleton]$ composer require aws/aws-sdk-php

Ok, I have the SDK installed and it looks I am ready to build an application that will work with Polly.

Let's think about it: I will have an Ajax module that is asking for customer's phone number and once you type phone number in the text box and hit the Enter key, it will post the phone number to the server and if there is match then will receive a success return with "Welcome, {customer_name}!" salutation as text. Polly is not involved so far.

I am working on a clean Zend Framework Skeleton Application. Since I will be returning Json from the controller, first thing is adding ViewJsonStrategy in the module configuration.

# /module/Application/src/config/module.config.php
<?php namespace Application; return [ // ... 'view_manager' => [ 'display_not_found_reason' => true, 'display_exceptions' => true, 'doctype' => 'HTML5', 'not_found_template' => 'error/404', 'exception_template' => 'error/index', 'template_map' => [ 'layout/layout' => __DIR__ . '/../view/layout/layout.phtml', 'application/index/index' => __DIR__ . '/../view/application/index/index.phtml', 'error/404' => __DIR__ . '/../view/error/404.phtml', 'error/index' => __DIR__ . '/../view/error/index.phtml', ], 'template_path_stack' => [ __DIR__ . '/../view', ], 'strategies' => [ 'ViewJsonStrategy', ], ], // ... ];

In fact, I also need zend-json module which is not installed with Skeleton as default.


[smozgur@local Skeleton]$ composer require zendframework/zend-json

Ok, we can dive into Controller now. I will do the demonstration by adding necessary actions into the default IndexController.

# /module/Application/src/Controller/IndexController.php
<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class IndexController extends AbstractActionController { public function indexAction() { return new ViewModel(); } public function checkInAction() { return new ViewModel(); } }

Nothing fancy here, added checkInAction function to catch /application/check-in route. And following is the simple view for this action:

# /module/Application/view/application/index/check-in.phtml
<div> <input id="txt-phone" type="text" placeholder="Enter phone number"> <button id="btn-submit" class="btn btn-sm btn-default">Submit</button> </div>

This is how the page looks.

I am ready to add Ajax script into the view template and action function into the IndexController which will send response to this Ajax call. New check-in.phtml:

# /module/Application/view/application/index/check-in.phtml
<div> <input id="txt-phone" type="text" placeholder="Enter phone number"> <button id="btn-submit" class="btn btn-sm btn-default">Submit</button> </div> <script type="text/javascript"> $('#btn-submit').click(function() { $.ajax({ method: 'POST', url: '/application/ajax-checkin', dataType: 'json', data: { phone: $('#txt-phone').val(), }, success: function(response){ if (response.success) { // Doing something for the matched phone number } else { // No match. } alert(response.message); }, }); }); </script>

And ajaxCheckInAction in the controller:

# /module/Application/src/Controller/IndexController.php
<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Zend\View\Model\JsonModel; class IndexController extends AbstractActionController { public function indexAction() { return new ViewModel(); } public function checkInAction() { return new ViewModel(); } public function ajaxCheckInAction() { /** * Return value will be a JSON encoded string * Using JsonModel() for Ajax returns * * @var \Zend\View\Model\JsonModel $response */ $response = new JsonModel(); /* * Return nothing if it is not an Ajax request */ if (!$this->getRequest()->isXmlHttpRequest()) { return $response; } /** * Getting requested phone number from the posted data * * @var string $phone */ $phone = (string) $this->params()->fromPost('phone', false); /** * Return error if phone is not given */ if (!$phone) { $response->setVariables([ 'success' => false, 'message' => 'Please provide your phone number' ]); return $response; } /** * Skipping model processing here and * assuming we found the customer * and returning the expected salutation. */ $customerName = 'Suat'; $response->setVariables([ 'success' => true, 'message' => sprintf('Welcome, %s', $customerName), ]); return $response; } }

This is what I get when I enter a phone number and click on the button.

Almost done. It is time to get Polly involved.

Adding my second Ajax call that will request for the audio file from the controller, by giving the customer id which will be returned by the first Ajax call.

# /module/Application/view/application/index/check-in.phtml
<div> <input id="txt-phone" type="text" placeholder="Enter phone number"> <button id="btn-submit" class="btn btn-sm btn-default">Submit</button> </div> <script type="text/javascript"> $('#btn-submit').click(function() { $.ajax({ method: 'POST', url: '/application/ajax-checkin', dataType: 'json', data: { phone: $('#txt-phone').val() }, success: function(response){ if (response.success) { // Doing something for the matched phone number // Make the second Ajax call to make it say something! sayIt(response.customerId) } else { // No match. } alert(response.message); }, }); }); function sayIt(customerId) { window.AudioContext = window.AudioContext || window.webkitAudioContext; var context = new AudioContext(); $.ajax({ method: 'POST', url: '/application/ajax-polly', dataType: 'binary', xhrFields : { responseType : 'arraybuffer' }, data: { id: customerId }, success: function(response){ context.decodeAudioData(response, function onSuccess(buffer) { var source = context.createBufferSource(); source.buffer = buffer; source.connect(context.destination); source.start(0); }, function onError (error) { alert('Error decoding file data.'); }); } }); } </script>

Here is a very good article about Web Audio API what I am using for playing the returned binary data by the controller action in this code.

And the revised IndexController that now includes ajaxPollyAction where the Polly magic happens:

# /module/Application/src/Controller/IndexController.php
<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Zend\View\Model\JsonModel; use Aws\Sdk; class IndexController extends AbstractActionController { public function indexAction() { return new ViewModel(); } public function checkInAction() { return new ViewModel(); } public function ajaxCheckInAction() { /** * Return value will be a JSON encoded string * Using JsonModel() for Ajax returns * * @var \Zend\View\Model\JsonModel $response */ $response = new JsonModel(); /* * Return nothing if it is not an Ajax request */ if (!$this->getRequest()->isXmlHttpRequest()) { return $response; } /** * Getting requested phone number from the posted data * * @var string $phone */ $phone = (string) $this->params()->fromPost('phone', false); /** * Return error if phone is not given */ if (!$phone) { $response->setVariables([ 'success' => false, 'message' => 'Please provide your phone number' ]); return $response; } /** * Skipping model processing here and * assuming we found the customer by the phone number * and returning the expected salutation. */ $customerId = 123; $customerName = 'Suat'; $response->setVariables([ 'success' => true, 'customerId' => $customerId, 'message' => sprintf('Welcome, %s', $customerName), ]); return $response; } public function ajaxPollyAction() { $response = $this->getResponse(); /* * Return nothing if it is not an Ajax request */ if (!$this->getRequest()->isXmlHttpRequest()) { return $response; } $id = $this->params()->fromPost('id', false); if ($id) { /** * Skipping model processing here and * assuming we found the customer by the provided id */ $customerName = 'Suat'; /** * AWS IAM credentials * * @var array $config */ $config = [ 'version' => 'latest', 'region' => 'us-east-1', 'credentials' => [ 'key' => '***IAMKEY***', 'secret' => '***IAMSECRET***', ]]; /** * Create AWS SDK by provinding the $config for authentication * * @var \Aws\Sdk $sdk */ $sdk = new Sdk($config); $pollyClient = $sdk->createPolly(); /** * Preparing required parameters in this array * OutputFormat for desired file type * Text to Speech * VoiceId one of the 47 lifelike voices in Polly * * @var array $args */ $args = [ 'OutputFormat' => 'mp3', 'Text' => sprintf('Welcome, %s!', $customerName) , 'VoiceId' => 'Joanna', ]; /** * Polly client's synthesizeSpeech method * returns the array contains AudioStream * that will be used as the audio file binary content * */ $pollyResponse = $pollyClient->synthesizeSpeech($args); $audioContent = $pollyResponse['AudioStream']; /** * Return audio content by setting necessary headers */ $response->setContent($audioContent); $response ->getHeaders() ->addHeaderLine('Content-Transfer-Encoding', 'chunked') ->addHeaderLine('Content-Type', 'audio/mpeg'); return $response; } } }

Finally I have an application speaking with me!


*** I'd be happy to know if this content helped you or anything else about it to tell me. Please also feel free to share it by using the buttons below if you think it might help others too.


 
Worked with Zend Framework 3