如何使用 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. 在注释为 1 的地方,我们先确保 nextSpeechUtterance 不会出现下标越界。
  2. 在注释为 2 的地方,我们获得当前要读到的 utterance,然后 nextSpeechUtterance 加1。
  3. 最终,注释为 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 字段。为此,作者不得不手工调整每一个词句。

如何使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(3)

时间: 2024-11-05 13:48:27

如何使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(3)的相关文章

如何使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(1)

原文: http://www.raywenderlich.com/64623/make-narrated-book-using-avspeechsynthesizer-ios-7 随着 PageViewController 的引入,苹果让开发者们制作图书类app 更加轻松.不幸的是,对于生活在朝九晚五繁忙节奏中的人们来说,阅读也是一件奢侈的事情.为什么你不能在读一本小说的同时做其他事情呢? 在 Siri 刚开始出现的时候,苹果曾经用复杂的动态文本阅读将开发者拒之门外,但当iOS7 发布的时候,苹

如何使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(2)

切分语句 软件工程的一条定律是数据和代码分离.这样做会使代码更易于测试,即使输入的数据发生改变,你的代码也能够允许.甚至于,程序能在运行中实时下载新的数据.如果程序能在运行中下载新书岂不是更好? 你现在用的书是用 Book.testBook 方法中的代码创建的.接下来我们将书改为以文件形式存储,读取的时候则通过Plist 文件来读取. 打开 SupportingFiles\WhirlySquirrelly.plist ,其内容如下: 你还可以通过右键->"Open As\Source Co

如何使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(4)

控制:我们必须学会控制 大师尤达(电影<星球大战>)曾有言:关键在于控制.这本故事书是一个词一个词地念出来的,我准备为它增加两个按钮,这样我们就可以实时地调整语音合成时的音高和语速. 仍然是 RWTPageViewController.m,在nextSpeechIndex 属性后声明下列属性: @property (nonatomic, assign) float currentPitchMultiplier; @property (nonatomic, assign) float curre

怎样使用 iOS 7 的 AVSpeechSynthesizer 制作有声书(2)

切分语句 软件project的一条定律是数据和代码分离.这样做会使代码更易于測试,即使输入的数据发生改变,你的代码也能够同意.甚至于,程序能在执行中实时下载新的数据.假设程序能在执行中下载新书岂不是更好? 你如今用的书是用 Book.testBook 方法中的代码创建的.接下来我们将书改为以文件形式存储,读取的时候则通过Plist 文件来读取. 打开 SupportingFiles\WhirlySquirrelly.plist ,其内容例如以下: 你还能够通过右键->"Open As\So

如何使用 iOS 7 的 AVSpeechSynthesizer 国家有声读物(4)

控制:我们一定要学会控制 尤达大师(电影<星球大战>)有话:的关键在于控制.这本故事书是一个字一个字读出来,我愿意为它添加两个button,音调和语速,以便我们能够调整语音合成实时的时候. 还是 RWTPageViewController.m,在nextSpeechIndex 属性后声明下列属性: @property (nonatomic, assign) float currentPitchMultiplier; @property (nonatomic, assign) float cur

iOS 推送证书制作(JAVA/PHP)

iOS 推送证书制作(JAVA/PHP) 在使用Java或者PHP制作iOS推送服务器的时候,需要自己从开发者网站上导出的aps_developer_identity证书和Apple Development Push Services证书进行合成,生成可以供Java使用的p12证书或供PHP使用的pem证书.aps_developer_identity证书和Apple Development Push Services证书的申请过程可以参考:http://www.cnblogs.com/hubj

AFNetworking是一个为 iOS 和 Mac OSX 制作的令人愉快的网络库

AFNetworking是一个为 iOS 和 Mac OSX 制作的令人愉快的网络库,它建立在URL 装载系统框架的顶层,内置在Cocoa里,扩展了强有力的高级网络抽象.它的模块架构被良好的设计,拥有丰富的功能,因此,使用起来,必定赏心悦目. @介绍 1.支持HTTP请求和基于REST的网络服务(包括GET.POST. PUT.DELETE等) 2.支持ARC 3.要求iOS 5.0及以上版本 4.UIKit扩展@配置1.下载AFNetworking,将2个文件夹:AFNetworking和UI

iOS 推送证书制作 (JAVA/PHP)

// aps_development.cer 转化成pem openssl x509 -in aps_development.cer -inform der -out PushChatCert.pem // p12 转化成pem openssl pkcs12 -nocerts -out PushChatKey.pem -in Push.p12 // Java服务器所需的证书为p12格式 openssl pkcs12 -export -in PushChatCert.pem -inkey Push

为什么人们爱听有声书?

今日导读 都说"一千个人心中就有一千个哈姆雷特",那么试问,在音频对白中听到的哈姆雷特,相比在舞台上.荧幕上看到的哈姆雷特,哪一种给你留下的印象会更深刻呢?单纯的听觉享受和多维的视觉呈现,哪项更重要?今天这篇来自<卫报>的新闻或许能给你不一样的启发. 新闻正文 ? Listen and weep: 'Audiobooks outdo films in emotional engagement' 闻者流泪(流血):有声书比电影更能调动你的感情 注:engagement:约会;