Twilio Base64 Media Payload for Google Speech To Text API not Responding - asp.net-core-mvc

I have a need to do some real time transcriptions from twilio phone calls using Google speech-to-text api and I've followed a few demo apps showing how to set this up. My application is in .net core 3.1 and I am using webhooks with a Twilio defined callback method. Upon retrieving the media from Twilio through the callback it is passed as Raw audio in encoded in base64 as you can see here.
https://www.twilio.com/docs/voice/twiml/stream
I've referenced this demo on Live Transcribing as well and am trying to mimic the case statement in the c#. Everything connects correctly and the media and payload is passed into my app just fine from Twilio.
The audio string is then converted to a byte[] to pass to the Task that needs to transcribe the audio
byte[] audioBytes = Convert.FromBase64String(info);
I am following the examples based of the Google docs that either stream from a file or an audio input (such as a microphone.) Where my use case is different is, I already have the bytes for each chunk of audio. The examples I referenced can be seen here. Transcribing audio from streaming input
Below is my implementation of the latter although using the raw audio bytes. This Task below is hit when the Twilio websocket connection hits the media event. I pass the payload directly into it. From my console logging I am getting to the Print Responses hit... console log, but it will NOT get into the while (await responseStream.MoveNextAsync()) block and log the transcript to the console. I do not get any errors back (that break the application.) Is this possible to even do? I have also tried loading the bytes into a memorystream object and passing them in as the Google doc examples do as well.
static async Task<object> StreamingRecognizeAsync(byte[] audioBytes)
{
var speech = SpeechClient.Create();
var streamingCall = speech.StreamingRecognize();
// Write the initial request with the config.
await streamingCall.WriteAsync(
new StreamingRecognizeRequest()
{
StreamingConfig = new StreamingRecognitionConfig()
{
Config = new RecognitionConfig()
{
Encoding =
RecognitionConfig.Types.AudioEncoding.Mulaw,
SampleRateHertz = 8000,
LanguageCode = "en",
},
InterimResults = true,
SingleUtterance = true
}
}); ;
// Print responses as they arrive.
Task printResponses = Task.Run(async () =>
{
Console.WriteLine("Print Responses hit...");
var responseStream = streamingCall.GetResponseStream();
while (await responseStream.MoveNextAsync())
{
StreamingRecognizeResponse response = responseStream.Current;
Console.WriteLine("Response stream moveNextAsync Hit...");
foreach (StreamingRecognitionResult result in response.Results)
{
foreach (SpeechRecognitionAlternative alternative in result.Alternatives)
{
Console.WriteLine("Google transcript " + alternative.Transcript);
}
}
}
});
//using (MemoryStream memStream = new MemoryStream(audioBytes))
//{
// var buffer = new byte[32 * 1024];
// int bytesRead;
// while ((bytesRead = await memStream.ReadAsync(audioBytes, 0, audioBytes.Length)) > 0)
// {
// await streamingCall.WriteAsync(
// new StreamingRecognizeRequest()
// {
// AudioContent = Google.Protobuf.ByteString
// .CopyFrom(buffer, 0, bytesRead),
// });
// }
//}
await streamingCall.WriteAsync(
new StreamingRecognizeRequest()
{
AudioContent = Google.Protobuf.ByteString
.CopyFrom(audioBytes),
});
await streamingCall.WriteCompleteAsync();
await printResponses;
return 0;
}

After all this, I discovered that this code works fine, just needs to be broken up and called in different events in the Twilio stream lifecycle.
The config section needs to be placed during the connected event.
The print messages task needs to be placed in the media event.
Then, the WriteCompleteAsync needs to be placed in the stop event when the websocket is closed from Twilio.
One other important item to consider are the number of requests being sent to Google STT to ensure that too many requests aren't overloading the quota which seems to be (for now) 300 requests / minute.

Related

is returning stream considered anti pattern in web api?

I am from the old world that think webapi should return a strong typed object and let json serialization return data.
However, recently we got this requirement:
We have a sql table which has more than 500 columns.
The customer always want to return all the columns.
Our c# code does nothing other than reading the SqlDatareader, convert the reader to a c# object and return result.
In this case, wouldn't better to do this (example copied from another stackoverflow post). Basically just return a stream? Does returning a stream still considered to be anti-pattern?
public HttpResponseMessage SomeMethod(List<string> someIds)
{
HttpResponseMessage resp = new HttpResponseMessage();
resp.Content = new PushStreamContent(async (responseStream, content, context) =>
{
await CopyBinaryValueToResponseStream(responseStream, someIds);
});
return resp;
}
private static async Task CopyBinaryValueToResponseStream(Stream responseStream, int imageId)
{
// PushStreamContent requires the responseStream to be closed
// for signaling it that you have finished writing the response.
using (responseStream)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand("SELECT 500 columns FROM [StupidWideTable] WHERE ....", connection))
{
.....
using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
{
if (await reader.ReadAsync())
{
if (!(await reader.IsDBNullAsync(0)))
{
using (Stream data = reader.GetStream(0))
{
// Asynchronously copy the stream from the server to the response stream
await data.CopyToAsync(responseStream);
}
}
}
}
}
}
}// close response stream
}
Does returning a stream still considered to be anti-pattern?
Well, that depends on what you want to do. For example, if you want to return a 500 if the SQL server fails partway through, then you shouldn't return a stream.
Streaming results works fine on ASP.NET, but it's important to note that all headers (including the response status code) are sent before the stream begins. So you'll send an immediate 200 when you start streaming the result, and if there's an error later on there's no way to go back in time and change that to a 500. Or add some kind of Continue header.
In other words, yes it's supported; but you lose all the benefits of model binding, content negotiation, exception handlers, etc., because you're bypassing that whole pipeline.

AWS Chime SDK js does not recognize video and audio elements

I am attempting to get the basic tutorial for the AWS Chime SDK to work in our application and the meetingSession.audioVideo.listVideoInputDevices() always returns nothing/null.
I am running this on lastest chrome, my operating system is a windows 10 workspace instance. I have headphones plugged in; but that shouldn't make a difference.
My expected result is to return at least one device for the video. Here is the output from the Logger.
2020-08-26T15:29:19.127Z [INFO] MyLogger - attempting to trigger media device labels since they are hidden
chime-bundle.js:1 2020-08-26T15:29:19.133Z [INFO] MyLogger - unable to get media device labels
chime-bundle.js:1 2020-08-26T15:29:19.134Z [INFO] MyLogger - API/DefaultDeviceController/listVideoInputDevices null -> []
chime-bundle.js:1 Uncaught (in promise) TypeError: Cannot read property 'deviceId' of undefined
*Note. The video and audio elements are not hidden.
I have tried the code snippits from various demos. Which are all just a copy of AWS's walkthrough. So pretty much zero information. I have researched how the audio devices work in html5 and looking through the files provided in the sdk-js, I am even more confused. Can someone point me in the right direction?
Here is the basic code, you can get it, and a description from the link above.
var fetchResult = await window.fetch(
window.encodeURI("<our endpoint for backend (running c# instead of node)>",
{
method: 'POST'
}
);
let result = await fetchResult.json();
console.log("Result from Chime API:", result);
const logger = new ConsoleLogger('MyLogger', LogLevel.INFO);
const deviceController = new DefaultDeviceController(logger);
const meetingResponse = result.JoinInfo.Meeting;
const attendeeResponse = result.JoinInfo.Attendee;
const configuration = new MeetingSessionConfiguration(meetingResponse, attendeeResponse);
// In the usage examples below, you will use this meetingSession object.
const meetingSession = new DefaultMeetingSession(
configuration,
logger,
deviceController
);
console.log("MEETING SESSION", meetingSession);
//SETUP AUDIO
const audioElement = document.getElementById('notary-audio');
meetingSession.audioVideo.bindAudioElement(audioElement);
const videoElement = document.getElementById('notary-video');
// Make sure you have chosen your camera. In this use case, you will choose the first device.
const videoInputDevices = await meetingSession.audioVideo.listVideoInputDevices();
// The camera LED light will turn on indicating that it is now capturing.
// See the "Device" section for details.
await meetingSession.audioVideo.chooseVideoInputDevice(videoInputDevices[0].deviceId);
const observer = {
audioVideoDidStart: () => {
console.log('Started');
},
audioVideoDidStop: sessionStatus => {
// See the "Stopping a session" section for details.
console.log('Stopped with a session status code: ', sessionStatus.statusCode());
},
audioVideoDidStartConnecting: reconnecting => {
if (reconnecting) {
// e.g. the WiFi connection is dropped.
console.log('Attempting to reconnect');
}
},
// videoTileDidUpdate is called whenever a new tile is created or tileState changes.
videoTileDidUpdate: tileState => {
// Ignore a tile without attendee ID and other attendee's tile.
if (!tileState.boundAttendeeId || !tileState.localTile) {
return;
}
// videoTileDidUpdate is also invoked when you call startLocalVideoTile or tileState changes.
console.log(`If you called stopLocalVideoTile, ${tileState.active} is false.`);
meetingSession.audioVideo.bindVideoElement(tileState.tileId, videoElement);
localTileId = tileState.tileId;
},
videoTileWasRemoved: tileId => {
if (localTileId === tileId) {
console.log(`You called removeLocalVideoTile. videoElement can be bound to another tile.`);
localTileId = null;
}
}
};
meetingSession.audioVideo.addObserver(observer);
meetingSession.audioVideo.start();

Can't able to send SMS using Twilio Trail Account using C#

I'm just trying to use Twilio to send transaction SMS. I have tried exactly the same code which is provided in the Twilio Documentation
static void Main(string[] args)
{
try
{
// Find your Account Sid and Token at twilio.com/console
const string accountSid = "AC5270abb139629daeb8f3c205ec632155";
const string authToken = "XXXXXXXXXXXXXX";
TwilioClient.Init(accountSid, authToken);
var message = MessageResource.Create(
from: new Twilio.Types.PhoneNumber("+15017122661"),
body: "Body",
to: new Twilio.Types.PhoneNumber("MyNumber")
);
Console.WriteLine(message.Sid);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
in this authToken copy from Twilio console and the TO number is my number which is used to register on Twilio. I also have verified the number in Verified Caller IDs segment in Twilio Console.
From Number initially, I was using the number which is generated by in Twilio Console the Number Belongs to the US but it won't work. After Reading this
Article I used the Exact code provided by Twilio just make the Changes as authToken and TO Number. But still, it won't work.
I have No idea why it Does not Work. is that you Can't Send the message from one country to another country?
As I want to Verify Mobile number by sending code from SMS. so achieve this I'm using
Twilio Verify API here where the Code is generated by Twilio and verified by himself.
this Solve my problem.
TO Send SMS :-
var client = new HttpClient();
var requestContent = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("via", "sms"),
new KeyValuePair<string,string>("phone_number", "Moblienumber"),
new KeyValuePair<string,string>("country_code", "CountryCode"),
});
// https://api.authy.com/protected/$AUTHY_API_FORMAT/phones/verification/start?via=$VIA&country_code=$USER_COUNTRY&phone_number=$USER_PHONE
HttpResponseMessage response = await client.PostAsync(
"https://api.authy.com/protected/json/phones/verification/start?api_key=" + "Your Key",
requestContent);
// Get the response content.
HttpContent responseContent = response.Content;
// Get the stream of the content.
using (var reader = new StreamReader(await responseContent.ReadAsStreamAsync()))
{
// Write the output.
Console.WriteLine(await reader.ReadToEndAsync());
}
return Ok();
To Verify :-
// Create client
var client = new HttpClient();
// Add authentication header
client.DefaultRequestHeaders.Add("X-Authy-API-Key", "Your Key");
// https://api.authy.com/protected/$AUTHY_API_FORMAT/phones/verification/check?phone_number=$USER_PHONE&country_code=$USER_COUNTRY&verification_code=$VERIFY_CODE
HttpResponseMessage response = await client.GetAsync(
"https://api.authy.com/protected/json/phones/verification/check?phone_number=phone_number&country_code=country_code&verification_code=CodeReceivedbySMS ");
// Get the response content.
HttpContent responseContent = response.Content;
// Get the stream of the content.
using (var reader = new StreamReader(await responseContent.ReadAsStreamAsync()))
{
// Write the output.
Console.WriteLine(await reader.ReadToEndAsync());
}
return Ok();

Should I create a .factory in order to share a variable's value to two ajax requests?

Using angularjs I have made two ajax json requests, the first request is retrieving channel's online / offline status and the second ajax json request is for the channel's info (logo, name, status etc).
I assigned the variable signal to 'data.stream' which is the online / offline status for channels to share signal between json requests. In Google Developer console I am receiving a value of null. I did some research here http://www.ng-newsletter.com/posts/beginner2expert-services.html and found that using a service might be a solution. I followed the directions but I'm still unable to get the scope between json request to recognize signal.
I read that rootscope could be used but it's not recommend, not sure how true that is because a novice using angular but I want start my angular journey by applying best practices.
Recap: Using Angular Ajax to make a jsonp request to twitch api, I am make two requests one to retrieve the online / offline status of channels in my streamers array, and the other ajax json request is to retrieve the channel's info, and I need the scope of the variable signal which has been assigned the value data.stream to be seen between ajax jsonp requests. Please let me know if this is a logical approach or if I'm "going around the world again".
Here is a plunker: plnkr.co/edit/6sb39NktX7CwfnQFxNNX
// creating a service for signal ?
app.factory('signal', function() {
var signal = {};
return signal;
})
// declaring my TwitchController and dependencies
app.controller('TwitchController', ['$scope', '$http', 'signal', function($scope, $http, signal) {
// streamers array with array of channels
var streamers = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff", "medrybw"];
$scope.imgs;
$scope.signal = signal;
var self = this;
//empty array
self.info = [];
for (var i = 0; i < streamers.length; i++) {
var url = "https://api.twitch.tv/kraken/channels/";
var streamUrl = "https://api.twitch.tv/kraken/streams/";
var callback = "/?callback=JSON_CALLBACK";
//json ajax request for channels online and offline status
$http.jsonp(streamUrl + streamers[i] + callback).success(function(data) {
//provides status of shows online and offline
signal = data.stream;
console.log(signal)
});
// json ajax request for channels and channel's info
$http.jsonp(url + streamers[i] + callback).success(function(data) {
// if channel does not have a logo image, this jpg will be the placeholder
// if statement test channel status (online or offline)
if (!signal) {
signal = "offline";
} else if (signal) {
signal = 'online';
}
// pushing retreive data from twitch.tv into array self.info
self.info.push(data);
});
Your issue here is async call to $http. You need to use $http.then and chain the promises. You do not need a factory for this instance but it is best practice to have one that just returns your info object. I didn't know exactly the format you wanted so I created one. Here is the plunker: http://plnkr.co/edit/ecwk0vGMJCvkqCbZa7Cw?p=preview
var app = angular.module('Twitch', []);
// declaring my TwitchController and dependencies
app.controller('TwitchController', ['$scope', '$http', function($scope, $http) {
// streamers array with array of channels
var streamers = ['freecodecamp', 'storbeck','terakilobyte', 'habathcx', 'RobotCaleb', 'thomasballinger', 'noobs2ninjas', 'beohoff', 'medrybw' ];
//empty array
$scope.info = [];
var url = 'https://api.twitch.tv/kraken/channels/';
var streamUrl = 'https://api.twitch.tv/kraken/streams/';
var callback = '/?callback=JSON_CALLBACK';
angular.forEach(streamers, function(stream) {
//json ajax request for channels online and offline status
$http.jsonp(streamUrl + stream + callback).then(function (data) {
//provides status of shows online and offline
// json ajax request for channels and channel's info
$http.jsonp(url + stream + callback).then(function (channelData) {
// pushing retrieve data from twitch.tv into array self.info
$scope.info.push(
{
url: url + stream,
stream: stream,
status: !data.stream ? 'offline' : 'online', // ternary statement test channel status (online or offline)
logo: channelData.data.logo ? channelData.data.logo : 'placeholderlogo.jpg' // if channel does not have a logo image, this jpg will be the placeholder
}
);
});
});
});
}]);

Read both key values and files from multipart from data post request in ASP.NET WebAPI

I have an endpoint that needs to accept a file upload and also some other information from the client request. With the following code I can upload the file successfully but can't seem to figure out how to read the other info.
I make a test request from Postman with the following form data:
image -- myimage.jpg -- of type File
email -- a#b.com -- of type Text
The backend code looks like this:
[HttpPost]
public async Task<HttpResponseMessage> SharePhoto()
{
try
{
var provider = new MultipartMemoryStreamProvider();
var data = await Request.Content.ReadAsMultipartAsync(provider);
// this is how I get the image which I am succesfully passing to EmailService
var item = (StreamContent)provider.Contents[0];
using (var stream = new MemoryStream())
{
await item.CopyToAsync(stream);
String emailAddress;
EmailService.SendSharedPhoto(emailAddress, stream);
return Request.CreateResponse();
}
}
catch
{
// do stuff
}
}
In this example I am able to access provider.Contents[1] but can't seem to be able to get the value from it into emailAddress. I'm thinking it may be possible to use the same trick as the await item.CopyToASync(stream) from the image upload, but I'm hoping I can get a simpler solution to that. Any ideas?
I just barely answered a very similar question to this yesterday. See my answer here complete with sample controller code.
The method I ended up using is:
If the form elements are strings (and it worked for me since the mobiel frontend took responsability for input data) you can do this:
var streamContent = (StreamContent)provider.Contents[1];
var memStream = new MemoryStream();
await streamContent.CopyToAsync(memStream);
var actualString = Encoding.UTF8.GetString(x.ToArray());
If however the field needs to represent a collection of items, like for example the email list: ["a#b.com", "x#c.com"], etc a JavaScriptSerializer can be user, like so:
var streamContent = (StreamContent)provider.Contents[1];
var emailAddresses = await str.ReadAsStringAsync();
var jsSerializer = new JavaScriptSerializer();
var deserializedData = jsSerializer.Deserialize<string[]>(emailAddresses);
Note that this is nowhere near safe, though it is few lines of code and happens to work.

Resources