How to stop the voice during text-to-speech synthesis? - ios

I used the following code to initial the text to speech synthesis using a button. But sometimes users may want to stop the voice in the middle of the speech. May i know is there any code i can do that.
Thanks
Here is my code
#interface RMDemoStepViewController ()
#end
#implementation RMDemoStepViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//Add Border to TextBox
//Instantiate the object that will allow us to use text to speech
self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
[self.speechSynthesizer setDelegate:self];
}
- (IBAction)speakButtonWasPressed:(id)sender{
[self speakText:[self.textView text]];
}
- (void)speakText:(NSString *)toBeSpoken{
AVSpeechUtterance *utt = [AVSpeechUtterance speechUtteranceWithString:toBeSpoken];
utt.rate = [self.speedSlider value];
[self.speechSynthesizer speakUtterance:utt];
}
- (IBAction)speechSpeedShouldChange:(id)sender
{
UISlider *slider = (UISlider *)sender;
NSInteger val = lround(slider.value);
NSLog(#"%#",[NSString stringWithFormat:#"%ld",(long)val]);
}
#end

But sometimes users may want to stop the voice in the middle of the speech.
To stop speech, send the speech synthesizer a -stopSpeakingAtBoundary: message:
[self.speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
Use AVSpeechBoundaryWord instead of AVSpeechBoundaryImmediate if you want the speech to continue to the end of the current word rather than stopping instantly.
You can also pause speech instead of stopping it altogether.

This will completely stop the SpeechSynthesizer: -stopSpeakingAtBoundary and if you want more code after the SS, then put : at the end of that. So basically, -stopSpeakingAtBoundary: For the full code to fit in with yours, here: [self.speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];

Related

How to disable seeking on iOS AVPlayerViewController?

On tvOS if I want to allow play / pause but disable seeking, I can set avPlayerViewController.requiresLinearPlayback = YES. However this property doesn't exist in iOS. How would I accomplish the same on iOS? I don't want to use avPlayerViewController.showsPlaybackControls = NO because this would prevent play / pause. I only want to disable seeking. Furthermore, I'm not entirely sure that avPlayerViewController.showsPlaybackControls = NO would even fully prevent seeking, as someone could plug in a headset with control buttons on it and press the physical "skip forward" button
I solved this problem by extending AVPlayer. Despite the Apple documentation saying that you should not extend AVPlayer and doing so has undefined behavior, in test everything worked fine on iOS 9 through 13
#interface CustomAVPlayer : AVPlayer
#property BOOL seekingEnabled;
#end
#implementation CustomAVPlayer
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
{
self = [super initWithPlayerItem:item];
if (self) {
_seekingEnabled = YES;
}
return self;
}
- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL))completionHandler
{
// This prevents seeking at the lowest level, regardless of whatever shenanigans the view may be doing
if (_seekingEnabled)
[super seekToTime:time toleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
else
[super seekToTime:self.currentTime toleranceBefore:toleranceBefore toleranceAfter:toleranceAfter completionHandler:completionHandler];
}
#end
Now I can enable/disable seeking with just: player.seekingEnabled = (YES/NO);

Overlaying background sound

we are having problem with our background sound. If the sound is on when we change view and then go back to the menu it adds another loop of sound. If the sound is muted when we go back it starts again. Please help. This is my code.
// Meny.m
#import "Meny.h"
#import <AVFoundation/AVFoundation.h>
#interface Meny () {
AVAudioPlayer *audioPlayerBG;
}
#end
#implementation Meny
- (void)viewDidLoad {
[super viewDidLoad];
NSString *music = [[NSBundle mainBundle]pathForResource:#"TestSwoong" ofType:#"wav"];
audioPlayerBG = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
audioPlayerBG.numberOfLoops = -1;
audioPlayerBG.volume = 0.5;
[audioPlayerBG play];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
// LjudKnapp stop
- (IBAction)stopBG:(id)sender {
[playBG setHidden:NO];
[pauseBG setHidden:YES];
[audioPlayerBG stop];
}
// LjudKnapp play
- (IBAction)playBG:(id)sender {
[pauseBG setHidden:NO];
[playBG setHidden:YES];
[audioPlayerBG play];
}
It appears that you need to brush up on what classes and instances are. Understanding classes and instances is fundamental if you're going to do object-oriented programming, which is what you do when you use Objective-C.
we change view and then go back to the menu it adds another loop of sound
That phrase suggests that when you "go back", you are not actually going back to the already existing Meny instance - instead, you are creating another Meny instance. So now, you see, you have two of them! Well, each Meny instance has its own AVAudioPlayer, which starts playing when the Meny instance is created — so now you have two audio players, and that's why you hear two loops playing simultaneously.

DTMF sounds for custom Dialer iOS

I am trying to add dtmf sounds to my custom dialer pad.
I managed reproducing a sound for each number on touch down. Now I am trying to achieve the natural phone behaviour for when the person who is dialing does a "longpress" on a number.
The problem is that the sound ends. How could I make it lasting as long as the touch ends?
In my code for now I am trying to add a long sound even if I am using the touch down event, just to test if it works. If it does I will add the gesture recognizer for long press.
Here is my code:
#interface ViewController : UIViewController<AVAudioPlayerDelegate>{
//SystemSoundID toneSSIDs[12];
AVAudioPlayer *toneSSIDs[12];
}
#end
#implementation ViewController
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
for(int count = 0; count < 12; count++){
NSString *toneFilename = [NSString stringWithFormat:#"dtmf-%d", count];
NSURL *toneURLRef = [[NSBundle mainBundle] URLForResource:toneFilename
withExtension:#"mp3"];
player = [[AVAudioPlayer alloc]initWithContentsOfURL:toneURLRef error:NULL ];
player.volume = 0.4f;
toneSSIDs[count] = player;
}
}
return self;
}
-(void)playSoundForKey:(int)key {
_sound = toneSSIDs[key];
_sound.delegate = self;
_sound.numberOfLoops = -1;
[_sound prepareToPlay];
[_sound play];
}
- (IBAction)touchNum:(id)sender {
[self playSoundForKey:[sender tag]];
}
I tried using both .wav and .mp3 sounds and also looping the sound but I get multiple sounds, not a long one as I need. Somebody help, please!

Pause Button for AVSpeechSynthesiser

At the moment I have a map with annotations and when a user clicks on the annotation an audio plays. I wanted to add a Play/Pause button but it's not working and I'm unsure as to why.
The AVSpeechSynthesizer
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)anView
{
//Get a reference to the annotation this view is for...
id<MKAnnotation> annSelected = anView.annotation;
//Before casting, make sure this annotation is our custom type
//(and not some other type like MKUserLocation)...
if ([annSelected isKindOfClass:[MapViewAnnotation class]])
{
MapViewAnnotation *mva = (MapViewAnnotation *)annSelected;
AVSpeechSynthesizer *synthesizer = [[AVSpeechSynthesizer alloc]init];
AVSpeechUtterance *utterance =
[AVSpeechUtterance speechUtteranceWithString:mva.desc];
utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:#"en-gb"];
[utterance setRate:0.35];
[synthesizer speakUtterance:utterance];
}
The Button
- (IBAction)pauseButtonPressed:(UIButton *)sender
{
[_synthesizer pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}
Right now nothing happens when I click it.
use this one for pause
- (IBAction)pausePlayButton:(id)sender
{
if([synthesize isSpeaking]) {
[synthesize pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:#""];
[synthesize speakUtterance:utterance];
[synthesize pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}
}
I don't think you're initializing _synthesizer. Try doing self.synthesizer = [[AVSpeechSynthesizer alloc] init]; instead of assigning the synth to a local variable.
I noticed that AVSpeechSynthesizer had a hard time shutting up during the 7.0 beta, but I find it hard to believe that such an egregious bug would last this long.
NB: you should probably shouldn't recreate the AVSpeechSynthesizer every time an annotation is tapped.
NB2: Once you've paused, I think you have to call continueSpeaking to restart.
For future help, when I clicked pause and resume the annotation worked however it wouldn't let me play another annotation as technically the first one is still running. I added a stop button with [_synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; to sort it out.

An issue with AVSpeechSynthesizer, Any workarounds?

I am using AVSpeechSynthesizer to play text. I have an array of utterances to play.
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 speaking. When I click the cancel button when the first utterance is spoken, the speech stops and it clears all the utterances in the queue. If I press the cancel button after the first utterance is spoken (i.e. second utterance), then stopping the speech does not flush the utterances queue. The code that I am using for this is:
[speech stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
Can someone confirm if this is a bug in the API or am I using the API incorrectly? If it is a bug, is there any workaround to resolve this issue?
I found a workaround :
- (void)stopSpeech
{
if([_speechSynthesizer isSpeaking]) {
[_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:#""];
[_speechSynthesizer speakUtterance:utterance];
[_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
}
}
Call stopSpeakingAtBoundary:, enqueue an empty utterance and call stopSpeakingAtBoundary: again to stop and clean the queue.
All answers here failed, and what I came up with is stopping the synthesizer and then re-instantiate it:
- (void)stopSpeech
{
if([_speechSynthesizer isSpeaking]) {
[_speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
_speechSynthesizer = [AVSpeechSynthesizer new];
_speechSynthesizer.delegate = self;
}
}
quite likely to be a bug, in that the delegate method synthesizer didCancelSpeechUtterance isn't called after the first utterance;
A workaround would be to chain the utterances rather than have them in an array and queue them up at once.
Use the delegate method synthesizer didFinishSpeechUtterance to increment an array pointer and speak the the next text from that array. Then when trying to stop the speech, set a BOOL that is checked in this delegate method before attempting to speak the next text.
For example:
1) implement the protocol in the view controller that is doing the speech synthesis
#import <UIKit/UIKit.h>
#import AVFoundation;
#interface ViewController : UIViewController <AVSpeechSynthesizerDelegate>
#end
2) instantiate the AVSpeechSynthesizer and set its delegate to self
speechSynthesizer = [AVSpeechSynthesizer new];
speechSynthesizer.delegate = self;
3) use an utterance counter, set to zero at start of speaking
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 delegate method didFinishSpeechUtterance to speak the next utterance from the array
of texts and increment the utterance counter
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance{
if(utteranceCounter < utterances.count){
AVSpeechUtterance *utterance = utterances[utteranceCounter];
[synthesizer speakUtterance:utterance];
utteranceCounter++;
}
}
5) to stop speaking, set the utterance counter to the count of the texts array and attempt to get the synthesizer to stop
utteranceCounter = utterances.count;
BOOL speechStopped = [speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
if(!speechStopped){
[speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryWord];
}
6) when speaking again, reset the utterance counter to zero
I did something similar to what SPA mentioned. Speaking one item at a time from a 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];
}
didCancelSpeechUtterance does not work with the same AVSpeechSynthesizer object even though the utterances are chained in the didFinishSpeechUtterance method.
-(void)speakInternal
{
speech = [[AVSpeechSynthesizer alloc] init];
speech.delegate = self;
[speech speakUtterance:[utterances objectAtIndex:position++]];
}
In speakInternal, I am creating AVSpeechSynthesizer object multiple times to ensure that didCancelSpeechUtterance works. Kind of a workaround.

Resources