iOS 10 来点不一样的推送 (2) - 语音提示

2017-06-22 17:25

转自我的 Blog: Danny's Dream

接着上篇文章,在一个交流群里有个小伙伴问,怎么实现支付宝类似收到钱之后的语音播放效果。

结合着之前对推送的研究,想到了两种实现方案:

  • 1.在 notification 的 extension 中将收到的内容播放出来。

  • 2.将文字转换成语音文件,保存在本地,然后替换为播放的提示音。

直接播放

AVFoundation

其实苹果有提供原生的文字转语音的功能,在 AVFoundation 框架中。简单的使用方法如下:

self.speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:@“收到人民币1000000"];

AVSpeechSynthesisVoice *voiceType = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"];
utterance.voice = voiceType;
//设置语速
utterance.rate *= 0.5;
//设置音量
utterance.volume = 0.6;

[self.speechSynthesizer speakUtterance:utterance];

看上去很简单的样子,让我们赶紧放进 extension 中试一下。在之前的基础上我们做一些修改,将播放操作封装成一个播放的方法。

- (void)readContent:(NSString*)str{    //AVSpeechUtterance: 可以假想成要说的一段话
    AVSpeechUtterance * aVSpeechUtterance = [[AVSpeechUtterance alloc] initWithString:str];

    aVSpeechUtterance.rate = AVSpeechUtteranceDefaultSpeechRate;    //AVSpeechSynthesisVoice: 可以假想成人的声音
    aVSpeechUtterance.voice =[AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];    //发音
    [self.aVSpeechSynthesizer speakUtterance:aVSpeechUtterance];

}

target 配置

在收到推送的时候,将推送的 body 读出来,想的还是美滋滋的。把 demo 运行起来的时候,发现收到推送后并没有声音。通过查阅资料,发现类似于这样的后台播放音乐,是需要一个 Background modes 的权限的,就是下面的第一个 Mode。【不过好像有人说,勾选了该权限可能会被拒】

iOS 10 来点不一样的推送 (2) - 语音提示0

音效不完整

然后我们再一次的尝试,这次可以播放出声音了,但是有个问题,就是声音播放到一半就停了,然后紧跟着的是推送的通知音。初步推测是播放其实也是在另一个线程中的进行的,当结束 extension 的操作弹出通知时,播放语音仍在进行中,会导致两个冲突,而系统通知的优先级更高,所以原来的语音会被拦截。这一步考虑的解决方法是,在 extension 中做一个延迟的操作,首先想到的是用 GCD 。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^{

        self.contentHandler(self.bestAttemptContent);

    });

假设播放的语音是 5秒,在调用播放 5 秒之后,再触发处理完通知的回调,这样虽然是解决了上述的问题,但是似乎不够的优雅,无法控制如果语音更长的情况。

自动结束

翻阅了文档,看看有没有可以收到播放完成的事件的地方。发现 AVSpeechSynthesizer 有一个 AVSpeechSynthesizerDelegate,将当前的 NotificationService 实现 AVSpeechSynthesizerDelegate,这样就能在下面的回调中结束播放成功时间,这样就能动态的控制通知展示的时间的。

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance;{

    NSLog(@"阅读完毕");
    self.contentHandler(self.bestAttemptContent);
}

这样就基本实现了我们想要的效果了!由于推送的特殊性,可以实现后台唤醒,所以当 app 运行在后台,或者 app 被 kill 了,仍然可以唤醒并播放语言!????

合成

考虑的是采用科大讯飞的语音合成 SDK,在 extension 中进行集成,然后转换成语音文件保存至本地,同时把推送的提示语音设置为该音频文件。由于科大讯飞注册太麻烦了,就没尝试(跑。。)。不过感觉理论上应该可以实现该功能,主要有问题的地方可能就是转换之后的语言文件是否能作为提示音的问题了。

总结

基本的功能已经实现,最新的代码已经提交到原先的 demo 中啦,但是需要注意的是 demo 不带证书,可以把相关代码拷到你自己的项目中去尝试,演示视频也传到 GitHub 中了。(如果感到有用的加个?吧 溜了 溜了)