Problem with AVSpeechSynthesizer, any workarounds?

I use AVSpeechSynthesizer to play text. I have a lot of sayings for the game.

NSMutableArray *utterances = [[NSMutableArray alloc] init]; for (NSString *text in textArray) { AVSpeechUtterance *welcome = [[AVSpeechUtterance alloc] initWithString:text]; welcome.rate = 0.25; welcome.voice = voice; welcome.pitchMultiplier = 1.2; welcome.postUtteranceDelay = 0.25; [utterances addObject:welcome]; } lastUtterance = [utterances lastObject]; for (AVSpeechUtterance *utterance in utterances) { [speech speakUtterance:utterance]; } 

I have a cancel button to stop talking. When I click the Cancel button when the first statement is pronounced, the speech stops and it clears all the statements in the queue. If I click the Cancel button after pronouncing the first utterance (i.e. the Second utterance), then stopping the speech does not equalize the pronunciation queue. The code I use for this:

  [speech stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; 

Can anyone confirm if this is an API error or am I using the API incorrectly? If this is a mistake, are there any solutions to this problem?

+8
ios cocoa-touch xcode
source share
5 answers

most likely to be a mistake, since the delegate method didCancelSpeechUtterance method is not called after the first statement;

A workaround would be to bind the statements, rather than cast them into an array, and queue them immediately.

Use the didFinishSpeechUtterance delegate synthesis method to increase the pointer to an array and infer the next text from this array. Then, trying to stop the speech, set the BOOL, which is checked in this delegate method, before attempting to pronounce the following text.

For example:

1) implement a protocol in a view controller that performs speech synthesis

 #import <UIKit/UIKit.h> @import AVFoundation; @interface ViewController : UIViewController <AVSpeechSynthesizerDelegate> @end 

2) create an instance of AVSpeechSynthesizer and set its delegate to self

 speechSynthesizer = [AVSpeechSynthesizer new]; speechSynthesizer.delegate = self; 

3) use the utterance counter set to zero at the beginning of the conversation

4) use an array of texts to speak

 textArray = @[@"Mary had a little lamb, its fleece", @"was white as snow", @"and everywhere that Mary went", @"that sheep was sure to go"]; 

5) add the delegate didFinishSpeechUtterance method to speak the next statement from an array of texts and increase the statement counter

 - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance{ if(utteranceCounter < utterances.count){ AVSpeechUtterance *utterance = utterances[utteranceCounter]; [synthesizer speakUtterance:utterance]; utteranceCounter++; } } 

5) to stop the conversation, set the utterance counter to the array of texts and try to stop the synthesizer

 utteranceCounter = utterances.count; BOOL speechStopped = [speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; if(!speechStopped){ [speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryWord]; } 

6), again, reset the utterance counter with zero

+5
source share

I found a workaround:

 - (void)stopSpeech { if([_speechSynthesizer isSpeaking]) { [_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@""]; [_speechSynthesizer speakUtterance:utterance]; [_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; } } 

Call stopSpeakingAtBoundary: start the empty statement, and call stopSpeakingAtBoundary: again to stop and clear the queue.

+22
source share

All the answers here have failed, and what I came up with is to stop the synthesizer and then recreate it:

 - (void)stopSpeech { if([_speechSynthesizer isSpeaking]) { [_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; _speechSynthesizer = [AVSpeechSynthesizer new]; _speechSynthesizer.delegate = self; } } 
+2
source share

I did something similar to what SPA was talking about. Speaking one point at a time from the loop. Here is the idea ..

 NSMutableArray *arr; //array of NSStrings, declared as property AVSpeechUtterance *currentUtterence; //declared as property AVSpeechSynthesizer *synthesizer; //property - (void) viewDidLoad:(BOOL)anim { [super viewDidLoad:anim]; synthesizer = [[AVSpeechSynthesizer alloc]init]; //EDIT -- Added the line below synthesizer.delegate = self; arr = [self populateArrayWithString]; //generates strings to speak } //assuming one thread will call this - (void) speakNext { if (arr.count > 0) { NSString *str = [arr objectAtIndex:0]; [arr removeObjectAtIndex:0]; currentUtterence = [[AVSpeechUtterance alloc] initWithString:str]; //EDIT -- Commentted out the line below //currentUtterence.delegate = self; [synthesizer speakUtterance:utteranc]; } } - (void)speechSynthesizer:(AVSpeechSynthesizer *)avsSynthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { if ([synthesizer isEqual:avsSynthesizer] && [utterance isEqual:currentUtterence]) [self speakNext]; } - (IBOutlet) userTappedCancelledButton:(id)sender { //EDIT <- replaced the object the method gets called on. [synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; [arr removeAllObjects]; } 
+1
source share

didCancelSpeechUtterance does not work with the same AVSpeechSynthesizer object, although statements are chained in the didFinishSpeechUtterance method.

 -(void)speakInternal { speech = [[AVSpeechSynthesizer alloc] init]; speech.delegate = self; [speech speakUtterance:[utterances objectAtIndex:position++]]; } 

In talkInternal, I create an AVSpeechSynthesizer object several times to make sure that didCancelSpeechUtterance works. Type of workaround.

+1
source share

All Articles