I want my image view to change with every sentence in str, but it doesn't change
let elements = ["1","2","3"]
var cx = 0
for str in components{
OutImage.image = UIImage(named: elements1[cx+1])
myUtterance = AVSpeechUtterance(string: str)
myUtterance.rate = 0.4
myUtterance.pitchMultiplier = 1.3
myUtterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
myUtterance.voice = voiceToUse
synth.speak(myUtterance)
}
You can't do that in a for loop, because speak is asynchronous, meaning that it will execute in "parallel" with the rest of your code. By the time your for loop finishes, the speech synthesiser probably didn't even start synthesising your first sentence!
The solution, is to use an AVSpeechSynthesizerDelegate. In particular, you need to implement this method:
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
didFinish utterance: AVSpeechUtterance)
This delegate method is called when the synthesiser finishes an utterance.
In the method, you can figure out which image should be shown by looking at the cx variable. Then you should increment cx. After that, using cx, get the next utterance to be spoken and call speak. You should also remember to check for whether cx is the end of the array!
Related
I use AVSpeechSynthesizer to play text books via audio.
private lazy var synthesizer: AVSpeechSynthesizer = {
let synthesizer = AVSpeechSynthesizer()
synthesizer.delegate = self
return synthesizer
}()
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(
language: languageIdentifier(from: language)
)
synthesizer.speak(utterance)
I want to update information in iPhone's default player view (probably naming is wrong 🙏):
indicate playing Chapter with some text
enable next button to play the next chapter
How can I accomplish this?
I really don't think you want to hack your way through this.. But if you really do I would:
Listen to remote commands (UIApplication.sharedApplication().beginReceivingRemoteControlEvents(), see Apple Sample Project
Set your properties on MPNowPlayingInfoCenter: MPNowPlayingInfoCenter.default().nowPlayingInfo[MPMediaItemPropertyTitle] = "Title"
Implement the AVSpeechSynthesizerDelegate and try to map the delegate functions to playback states and estimate the playback progress using speechSynthesizer(_:willSpeakRangeOfSpeechString:utterance:) (idk if possible)
You might have to play with the usesApplicationAudioSession property of AVSpeechSynthesizer to have more control over the audio session (set categories etc.)
In order to exert greater control over speech in the spirit of this tutorial for an audiobook although I'm not following it exactly, I have tried sending smaller pieces of a string such as phrases in separate chunks. The speech synthesizer enqueues each utterance and speaks them one after the other. In theory, this is supposed to give you greater control to make speech sound less robotic.
I can get the synthesizer to speak the chunks in order however there is a long delay between each so it sounds way worse than just sending all the text at the same time.
Is there anyway to speed up the queue so that the utterances are spoken one after the other with no delay?
Setting the properties: utt.preUtteranceDelay and utt.postUtteranceDelay to zero seconds does not seem to have any effect
Here is my code:
phraseCounter = 0
func readParagraph(test: String) {
let phrases = test.components(separatedBy: " ")
for phrase in phrases {
phraseCounter = phraseCounter+1
let utt = AVSpeechUtterance(string:phrase)
let preUtteranceDelayInSecond = 0
let postUtteranceDelayInSecond = 0
utt.preUtteranceDelay = TimeInterval.init(exactly:preUtteranceDelayInSecond)!
utt.postUtteranceDelay = TimeInterval.init(exactly:postUtteranceDelayInSecond)!
voice.delegate = self
if (phraseCounter == 2) {
utt.rate = .8
}
voice.speak(utt)
}
}
Is there anyway to speed up the queue so that the utterances are spoken one after the other with no delay?
As you did, the only way is to set the post and pre UtteranceDelay properties to 0 which is the default value by the way.
As recommended here, I implemented the code snippet hereafter (Xcode 10, Swift 5.0 and iOS 12.3.1) to check the impact of different UtteranceDelay values âźą 0 is the best solution to improve the speed of enqueued utterances.
var synthesizer = AVSpeechSynthesizer()
var playQueue = [AVSpeechUtterance]()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
for i in 1...10 {
let stringNb = "Sentence number " + String(i) + " of the speech synthesizer."
let utterance = AVSpeechUtterance(string: stringNb)
utterance.rate = AVSpeechUtteranceDefaultSpeechRate
utterance.pitchMultiplier = 1.0
utterance.volume = 1.0
utterance.postUtteranceDelay = 0.0
utterance.preUtteranceDelay = 0.0
playQueue.append(utterance)
}
synthesizer.delegate = self
for utterance in playQueue {
synthesizer.speak(utterance)
}
}
If a delay is too important with the '0' value in your code, the incoming string is maybe the problem? (adapt the code snippet above to your needs)
I am setting up a tts app with AVSpeechSynthesizer. I have to do real-time pitch and rate adjustments. I am using UISLider for adjusting pitch and rate.
Here is my code:-
#IBAction func sl(_ sender: UISlider) {
if synthesizer.isSpeaking {
synthesizer.stopSpeaking(at: .immediate)
self.rate = sender.value
if currentRange.length > 0 {
let valuee = currentRange.length + currentRange.location
let neww = self.tvEditor.text.dropFirst(valuee)
self.tvEditor.text = String(neww)
synthesizer.speak(buildUtterance(for: rate, pitch: pitch, with: String(neww), language: self.preferredVoiceLanguageCode2 ?? "en"))
}
} else {
}
}
I may have understood your problem even if no details are provided: you can't take into account the new values of the rate and pitchMultiplier when the speech is running.
To explain the following details, I read this example that contains code snippets (ObjC, Swift) and illustrations.
Create your AVSpeechUtterance instances with their rate and pitchMultiplier properties.
Add each one of them in an array that will represent the queue to be spoken.
Make a loop inside the previous queue with the synthesizer to read out every elements.
Now, if you want to change the property values in real-time, see the steps hereafter once one of your sliders moves:
Get the current spoken utterance thanks to the AVSpeechSynthesizerDelegate protocol.
Run the stopSpeaking synthesizer method that will remove from the queue the utterances that haven't been spoken yet.
Create the previous removed utterances with the new property values.
Redo steps 2/ and 3/ to resume where you stopped with these updated values.
The synthesizer queues all information to be spoken long before you ask for new values that don't impact the stored utterances: you must remove and recreate the utterances with their new property values to be spoken.
If the code example provided by the link above isn't enough, I suggest to take a look at this WWDC video detailed summary dealing with AVSpeechSynthesizer.
I am stuck a little bit.
Here is my code:
let speaker = AVSpeechSynthesizer()
var playQueue = [AVSpeechUtterance]() // current queue
var backedQueue = [AVSpeechUtterance]() // queue backup
...
func moveBackward(_ currentUtterance:AVSpeechUtterance) {
speaker.stopSpeaking(at: .immediate)
let currentIndex = getCurrentIndexOfText(currentUtterance)
// out of range check was deleted
let previousElement = backedQueue[currentIndex-1]
playQueue.insert(previousElement, at: 0)
for utterance in playQueue {
speaker.speak(utterance) // error here
}
}
According to the docs AVSpeechSynthesizer.stopSpeaking(at:):
Stopping the synthesizer cancels any further speech; in constrast with
when the synthesizer is paused, speech cannot be resumed where it left
off. Any utterances yet to be spoken are removed from the
synthesizer’s queue.
I always get the error(AVSpeechUtterance shall not be enqueued twice), when I insert an AVSpeechUtterance in the AVSpeechSynthesizer queue. But it should stop according to the doc.
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 of playQueue[1] (same memory address).
Insert backedQueue[1] at playQueue[0] (former playQueue[1] becomes new playQueue[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.
I have this code to read my text:
var utterance = AVSpeechUtterance()
var synthesizer = AVSpeechSynthesizer()
utterance = AVSpeechUtterance(string: "\(textCount[modeIndex][0])")
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
utterance.rate = 0.4
synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
But in my text I want do long pause after some words. How to do it? Maybe there are some symbol to do this (this -- is not long)?
Based on the docs at https://developer.apple.com/documentation/avfoundation/avspeechutterance#1668645
You are looking to split your utterance into multiple queued utterances, and use the postUtteranceDelay:
var postUtteranceDelay: TimeInterval
The amount of time a speech synthesizer will wait after the utterance is spoken before handling the next queued utterance.