AVSpeechSynthesizer is not working After use one time
You need to set the AVAudioSession
for performing such tasks. Here is my working code. Hope this helps.
func speakText(voiceOutdata: String ) {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: .default, options: .defaultToSpeaker)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
} catch {
print("audioSession properties weren't set because of an error.")
}
let utterance = AVSpeechUtterance(string: voiceOutdata)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
let synth = AVSpeechSynthesizer()
synth.speak(utterance)
defer {
disableAVSession()
}
}
private func disableAVSession() {
do {
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
} catch {
print("audioSession properties weren't disable.")
}
}
An issue with AVSpeechSynthesizer, Any workarounds?
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
AVSpeechSynthesizer error: An AVSpeechUtterance shall not be enqueued twice
When stopping the player, the utterances are definitely removed from the queue.
However, in your moveBackward
function, you insert another AVSpeechUterrance
at playQueue[0]
whose complete array represents the player queue.
Assuming the stops happens with currentIndex = 2
, the following snapshots prove that the same object is injected twice in the queue:
Copy
backedQueue[1]
that is a copy ofplayQueue[1]
(same memory address).Insert
backedQueue[1]
atplayQueue[0]
(formerplayQueue[1]
becomes newplayQueue[2]
).
Unfortunately, as the system indicates, AVSpeechUtterance shall not be enqueued twice and that's exactly what you're doing here: objects at playQueue indexes 0 and 2 have the same memory address.
The last loop after inserting the new object at index 0 asks the speech synthesizer to put all the utterances in its all new queue... and two of them are the same.
Instead of copying the playedQueue
into the backedQueue
(both contain the same memory addresses to their objects) OR appending the same utterance in both arrays, I suggest to create different utterance instances to be put as follows:
for i in 1...5 {
let stringNb = "number " + String(i) + " of the speech synthesizer."
let utterance = AVSpeechUtterance(string: stringNb)
playQueue.append(utterance)
let utteranceBis = AVSpeechUtterance(string: stringNb)
backedQueue.append(utteranceBis)
}
Following this piece of advice, you shouldn't meet the error AVSpeechUtterance shall not be enqueued twice.
AVSpeechSynthesizer freezes iOS app for a few seconds
Looks like it's connected with the internal AVSpeechSynthesizer
queue. You can try to make it manage the thing in background, since there's no mention about AVSpeechSynthesizer
is main thread only.
Do so by adding the last line (synthesizer.speak(utterance)
) call to the background queue capturing references to the synthesizer
and utterance
objects like that:
DispatchQueue.global(qos: .background).async {
synthesizer.speak(utterance)
}
I think the reason why you have ui blocked is because synthesizer
instance has to block current thread to speak. So you must wait till synthesizer finish speaking before being deallocated on the exit from your speak()
method.
Speech Synthesis on iOS weird errors on loading, and no concurrency
As for #1, it's probably not gonna happen. The speech synthesizer is a system shared resource, so how the system handles scheduling multiple requests is out of our control as clients of the API. (Note that if you reuse the same synthesizer, it queues up extra utterances, but if you create multiple synthesizers, it fails to speak utterances that are requested while another synthesizer is speaking.)
Dunno about #2, sorry. Looks like diagnostic text, not necessarily an error. Probably worth filing a bug about, since they probably don't want to be logging diagnostics when there's no actual problem.
Bonus answer: You can use functional programming to make the selection of voice a bit shorter:
let voice = AVSpeechSynthesisVoice.speechVoices().first(where: { $0.name == "Arthur" })
Related Topics
Curl with Alamofire - Swift - Multipart/Form-Data
Swift Firebase Custom Object with Document Id
Uirefreshcontrol() in iOS 11 Glitchy Effect
Why Does Using Dynamictype on a Force Unwrapped Nil Optional Value Type Work
Countforfetchrequest in Swift 2.0
Typealias of Generic Class in Swift
Swift Protocol for String Interpolation
How to Use a Value Type Object as a Reference Type
Using Custom Cifilter on Calayer Shows No Change to Calayer
How to Grant Discoveruserinfowithuserrecordid Permission
Create Spotlight-Like Window in Swift 4
How to Replace Limited Number of Occurrences in String
Programmatically Create an Nsviewcontroller Without an Xib in Swift 3
Swift Conditional Conformances with Generic Type
Remove Programmatically Added Uiimageview
Swift Safely Unwrapping Optinal Strings and Ints
Create a New Window with Nswindow
Swift Convert Decimal Coordinate into Degrees, Minutes, Seconds, Direction