question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Only getting the audio of the last request when doing multiple requests at once

See original GitHub issue

When doing multiple requests at once, using Promise.all, I seem to only get the audioContent of the last resolving request. It seems like the underlying code the library uses cannot handle concurrent requests?

I’m synthesizing large text’s and need to split it up using the API’s character limit. I had this working before, so I know it should work, but stopped working recently.

Am I doing something wrong here? Or is this a bug?

I’m doing the exact same with Amazon’s Polly, and there it works. It’s exactly the same code, but with a different client and different request options.

So that made me think maybe it’s a library thing? Or a Google service issue?

Environment details

  • OS: Mac
  • Node.js version: 10.15.0
  • npm version: 6.8.0
  • @google-cloud/text-to-speech version: 0.5.1

Steps to reproduce

export const googleSsmlToSpeech = async (
  index: number,
  ssmlPart: string,
  type: SynthesizerType,
  identifier: string,
  synthesizerOptions: GoogleSynthesizerOptions,
  storageUploadPath: string
) => {
  let extension = 'mp3';

  if (synthesizerOptions.audioConfig.audioEncoding === 'OGG_OPUS') {
    extension = 'opus';
  }

  if (synthesizerOptions.audioConfig.audioEncoding === 'LINEAR16') {
    extension = 'wav';
  }

  synthesizerOptions.input.ssml = ssmlPart;

  const tempLocalAudiofilePath = `${appRootPath}/temp/${storageUploadPath}-${index}.${extension}`;

  try {
    // Make sure the path exists, if not, we create it
    await fsExtra.ensureFile(tempLocalAudiofilePath);

      // Performs the Text-to-Speech request
    const [response] = await client.synthesizeSpeech(synthesizerOptions);

    // Write the binary audio content to a local file
    await fsExtra.writeFile(tempLocalAudiofilePath, response.audioContent, 'binary');

    return tempLocalAudiofilePath;
  } catch (err) {
    throw err;
  }
};

/**
 * Synthesizes the SSML parts into seperate audiofiles
 */
export const googleSsmlPartsToSpeech = async (
  ssmlParts: string[],
  type: SynthesizerType,
  identifier: string,
  synthesizerOptions: GoogleSynthesizerOptions,
  storageUploadPath: string
) => {
  const promises: Promise<string>[] = [];
  
  // Overwrite for demo purposes
  ssmlParts = [
    '<speak><p>this</p></speak>',
    '<speak><p>is a</p></speak>',
    '<speak><p>bit of a longer test</p></speak>'
  ];

  ssmlParts.forEach((ssmlPart: string, index: number) => {
    promises.push(googleSsmlToSpeech(index, ssmlPart, type, identifier, synthesizerOptions, storageUploadPath));
  });

  const tempAudioFiles = await Promise.all(promises);

  tempAudioFiles.sort((a: any, b: any) => b - a); // Sort: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 etc...

  return tempAudioFiles;
};

The above code creates multiple files with the correct naming and index number, however, they all contain the same audio. That is; the audio response of the last request (<speak><p>bit of a longer test</p></speak>).

824163ed-b4d9-4830-99da-6e6f985727e2-0.mp3
824163ed-b4d9-4830-99da-6e6f985727e2-1.mp3
824163ed-b4d9-4830-99da-6e6f985727e2-2.mp3

Replacing the Promise.all with a simple for loop, makes it work. But this takes longer as it waits for every request to resolve. I know a Promise.all can work, because I had it working before.

  const tempAudioFiles = [];
  for (var i = 0; i < ssmlParts.length; i++) {
    tempAudioFiles[i] = await googleSsmlToSpeech(i, ssmlParts[i], type, identifier, synthesizerOptions, storageUploadPath);
  }

edit: updated the title and first paragraph with new findings. It seems to always return just the last one, even if it contains more text/audio then the other requests.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
alexander-fenstercommented, Apr 30, 2019

So what happens is that the API method you’re calling (client.synthesizeSpeech) is asynchronous inside (makes several network calls and uses promises). It’s likely that it yields the control flow before the actual request is sent to the server over gRPC, and this yielding is enough for the outer forEach loop to reassign the request parameter. You should not expect that your request parameter will be sent to the server right away without a context switch. Using unique object for each request is the best option.

Thanks!

0reactions
alexander-fenstercommented, Apr 30, 2019

To give you even more details, in Node the resolution of the promise is always asynchronous: in promise.then(func), func will be always executed after then() returns, on the next tick. To demonstrate:

process.nextTick(() => { console.log('next tick happened'); });

const promise = Promise.resolve();
console.log('promise created');
promise.then(() => { console.log('promise resolved'); });

It will print next tick happened before it prints promise resolved. Since we do have a promise and.then() in the method implenentation, this is where we yield the execution. We have a plan to change that.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Only getting the audio of the last request when doing multiple ...
When doing multiple requests at once, using Promise.all , I seem to only get the audioContent of the last resolving request.
Read more >
Multiple requests in Swift with se… | Apple Developer Forums
Faced with the need of making a serie of REST calls to get data before displaying it in a UITableViewController. It's a bit...
Read more >
Handling multiple ad requests with UserContext | IMA SDK for ...
Since ad requests are made asynchronously, ensuring the proper ad manager is associated with the correct context can seem to be a daunting...
Read more >
How can I send multiple guest audio requests at the same time?
Only one guest audio request can be active at a time, per presentation. If you generate a new voice request, the previous request...
Read more >
Speech-to-Text request construction - Google Cloud
Learn how to convert sound to text using Speech-to-Text. ... or gRPC request is quite similar. Streaming Recognition Requests are only supported by...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found