怎样使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(3)
plist 中的每一页 utteranceSting 我们都创建了一个RWTPage.displayText。因此,每页的文本会一次性地显示出来。
由于
You’ve constructedeach RWTPage.displayTextfrom the combined utteranceStringsfor the page in the plist. So, your page view displays the entire page’s text.
However, remember that RWTPageViewController.speakNextUtterancecreates a single AVSpeechUtterancefor the entire RWTPage.displayText.The result is that it overlooks your carefully parsed utterance properties.
In order to modifyhow each utterance is spoken, you need to synthesize each page’s text asindividual utterances. If only there were some way to observe and control howand when AVSpeechSynthesizerspeaks. Hmmm…
实现托付
AVSpeechSynthesizer 有一个托付AVSpeechSynthesizerDelegate ,在合成器生命周期中它负责通知各种重要的事件和动作。通过实现这些托付方法,我们可以让声音更自然。
打开 RWTPage.h。增加下列代码:
@property (nonatomic, strong, readonly) NSArray *utterances; |
打开 RWTPage.m ,增加下列代码:
@property (nonatomic, strong, readwrite) NSArray *utterances; |
注意这个技巧:将一个属性在头文件里声明为仅仅读。而在实现文件里声明为可读可写。这相当于仅仅有对象自己可以写这个属性。
将 pageWithAttribute:方法改动为:
+ (instancetype)pageWithAttributes:(NSDictionary*)attributes { RWTPage *page = [[RWTPage alloc] init]; if ([[attributes objectForKey:RWTPageAttributesKeyUtterances] isKindOfClass:[NSString class]]) { page.displayText = [attributes objectForKey:RWTPageAttributesKeyUtterances]; page.backgroundImage = [attributes objectForKey:RWTPageAttributesKeyBackgroundImage]; // 1 page.utterances = @[[[AVSpeechUtterance alloc] initWithString:page.displayText]]; } else if ([[attributes objectForKey:RWTPageAttributesKeyUtterances] isKindOfClass:[NSArray class]]) { NSMutableArray *utterances = [NSMutableArray arrayWithCapacity:31]; NSMutableString *displayText = [NSMutableString stringWithCapacity:101]; for (NSDictionary *utteranceAttributes in [attributes objectForKey:RWTPageAttributesKeyUtterances]) { NSString *utteranceString = [utteranceAttributes objectForKey:RWTUtteranceAttributesKeyUtteranceString]; NSDictionary *utteranceProperties = [utteranceAttributes objectForKey:RWTUtteranceAttributesKeyUtteranceProperties]; AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:utteranceString]; [utterance setValuesForKeysWithDictionary:utteranceProperties]; if (utterance) { [utterances addObject:utterance]; [displayText appendString:utteranceString]; } } page.displayText = displayText; page.backgroundImage = [UIImage imageNamed:[attributes objectForKey:RWTPageAttributesKeyBackgroundImage]]; // 2 page.utterances = [utterances copy]; } return page; } |
新加的代码位于凝视 1 和凝视 2 处。它们依据NSString 或NSArray的情况来设置 page.utterances 属性。
打开 RWTPageViewController.h ,改动为例如以下内容:
#import <UIKit/UIKit.h> @import AVFoundation; // 1 @interface RWTPageViewController : UIViewController<AVSpeechSynthesizerDelegate> @property (nonatomic, weak) IBOutlet UILabel *pageTextLabel; @property (nonatomic, weak) IBOutlet UIImageView *pageImageView; @end |
在凝视 1 处,声明了RWTPageViewController 实现了AVSpeechSynthesizerDelegate 协议。
打开 RWTPageViewController.m 增加:
@property (nonatomic, assign) NSUInteger nextSpeechIndex; |
我们用一个属性来记录下一个要读到的 RWTPage.utterances 索引。
改动 setupForCurrentPage方法:
- (void)setupForCurrentPage { self.pageTextLabel.text = [self currentPage].displayText; self.pageImageView.image = [self currentPage].backgroundImage; self.nextSpeechIndex = 0; } |
改动 speakNextUtterance方法:
- (void)speakNextUtterance { // 1 if (self.nextSpeechIndex < [[self currentPage].utterances count]) { // 2 AVSpeechUtterance *utterance = [[self currentPage].utterances objectAtIndex:self.nextSpeechIndex]; self.nextSpeechIndex += 1; // 3 [self.synthesizer speakUtterance:utterance]; } } |
- 在凝视为 1 的地方,我们先确保 nextSpeechUtterance 不会出现下标越界。
- 在凝视为 2 的地方,我们获得当前要读到的 utterance。然后 nextSpeechUtterance 加1。
- 终于,凝视为 3 的地方。朗读 utterance。
编译执行,听到了吗?在念第一页的时候。你仅仅听见一个词“Whisky”。由于我们还没有实现其它的托付方法,因此当一个utterance 念完后,并不会接着念下一个 utterance。
改动 startSpeaking方法:
- (void)startSpeaking { if (!self.synthesizer) { self.synthesizer = [[AVSpeechSynthesizer alloc] init]; // 1 self.synthesizer.delegate = self; } [self speakNextUtterance]; } |
凝视“1”的地方,我们将 ViewController 作为合成器的托付。
在 RWTPageViewController.m 最后增加一个方法:
#pragma mark - AVSpeechSynthesizerDelegate Protocol - (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance*)utterance { NSUInteger indexOfUtterance = [[self currentPage].utterances indexOfObject:utterance]; if (indexOfUtterance == NSNotFound) { return; } [self speakNextUtterance]; } |
这段代码在当前 utterance (文本)念完后,開始念下一段。
编译执行。如今的效果是:
- 一旦当前文本念完,自己主动跳到下一文本。直至这一页都念完。
- 不管向前还是向后翻页时。这一页的内容都不再继续念了。
- 发声更自然。这归功于 WhirlySquirrelly.plist文件里的 utteranceProperties 字段。为此,作者不得不手工调整每个词句。
posted on 2017-08-17 09:38 yjbjingcha 阅读(224) 评论(0) 编辑 收藏 举报