iOS开发 - 语音播报功能的实现

近期项目中有个需求就是要实现类似微信或者支付宝的收款时的语音播报功能,于是笔者就开始了漫长的踩坑之路。

一、最初的预想方案

刚开始讨论实现方案时,安卓的小伙伴说可以使用WebSocket + 讯飞语音在线合成实现。于是最初的几天笔者自己也一直在这条路上走了很久,基本功能都已经实现了,项目在前台的时候,基本没问题。但是项目一进入后台大概半分钟的时间,就无法播报了。原因是iOS项目如果不做任何处理的话,在进入后台大概30s之后,程序就会进入类似休眠的状态,然后就不会再进行任何操作了

二、开始想办法让程序在后台运行

跟安卓的同事讨论之后,发现安卓有方法可以让程序一直在后台处于活跃状态,于是笔者也开始找寻保持项目后台运行的方法,大概有两种

  1. 通过UIBackgroundTaskIdentifier不断向程序索要处理时间(这种方案不知道以前是否可行,现在好像是最多只能保持3分钟的时间)

  2. 通过后台音乐或者后台定位的方式保持程序后台长时间运行
    这种方案确实是可以在后台长时间运行的。
    定位的话,会在状态栏一直有一个蓝色的指示后台定位功能的横条,这个首先被pass
    然后是后台播放无声音乐的形式,此种方案理论上可行,也有小伙伴在企业包里面这样做,但是有的小伙伴反应这样在审核的时候可能会悲剧,所以也pass掉

三、寻求新的方案

  1. WebSocket方案行不通,笔者开始考虑采取 推送 + 讯飞语音在线合成的方案
    我们知道APNS收到推送之后在Appdelegate中有两个回调方法
/// 程序在后台时点击推送弹窗的回调方法
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

}
// 程序在前台时收到推送时的回调方法
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
}

在这里我们并没有发现,程序在后台收到推送时,作相应处理的方法,哪到底能不能收到推送后就进行处理呢?

答案是可以

iOS 10 之后 iOS推出了Notification Service Extension,我们可以在收到推送之后,通过这个Extension 我们可以有三十秒的时间来对这个推送进行处理

1
2
3

4

完成之后长这样


5

然后我们配置一下NotificationService


6
7

然后我们看下NotificationService.swift文件

import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here...
            bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
            // 在这里我们有大概30秒的时候对此条通知进行处理,如修改通知的title,body等等,contentHandler方法调用之后,我们修改过后的通知将会被发出
            contentHandler(bestAttemptContent)
        }
    }
    // 30s的处理时间即将结束时,该方法会被调用,最后一次提醒用户去做处理
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

}

四、NotificationService 调试

  1. 运行你的主程序
  2. 运行NotificationService extension 选择你的主程序
  3. Debug -> Attach To Process By PID or Name... -> 输入你的主程序项目名称 点击Attach

在完成上述操作之后,再次收到推送的话,就会走NotificationService的逻辑了,可以打断点或者Log测试一下

需要注意的是 在推送的内容中 必须配置mutable-content字段,结构大致如下

aps =     {
        alert =         {
            subtitle = "\U5fae\U4fe1\U5230\U8d260.01\U5143";
            title = "\U4eac\U8d1d\U5c14\U4e91\U5e97";
        };
        "mutable-content" = 1;
        sound = default;
    };

五、语音应该怎么播报?

做完上边的操作之后,我们可以知道什么时候去播报语音了,但是语音又要怎么去播报呢?
笔者这边也是试过几个方案,下边一一说来

  1. 使用讯飞语音在线合成直接播放 或者 使用苹果系统自带的AVSpeech

笔者刚开始使用讯飞发现不行,然后又测试了系统自带的AVSpeech,发现也不好用,查资料才知道,苹果在近期的版本中,停用的在NotificationService中播放语音的功能,之前的某个版本应该可以这么操作。好吧,此方案Pass

  1. 使用讯飞语音合成之后存到本地, 然后将语音文件设置成推送的sound

既然不让我播,那我存起来总可以了吧,测试发现讯飞在线生成是可以的,也可以存到本地,但。。。是,UNMutableNotificationContent的sound好像只支持提前添加到项目中的文件,并不支持立即生成之后存到本地,然后再设置的功能。。。

  1. 在请教了朋友之后,找到了一个笨方法,就是提前将 0-9,个十百千万这种可能会遇到的事先生成语音包,添加到NotificationService中,然后再收到的金额转成文字,分割之后通过对比取对应的语音包,随后发送多个本地通知(不设置title,subTitle等,只设置sound)这样就可以在收到推送之后,通过随后自己处理的多个推送连贯起来就可以实现我们所需要的语音播报了

笔者在项目中预先生成的文件如下(语音包通过百度语音开放平台在线生成百度语音在下生成(拉到中间就有了)

语音文件列表

比如说我要播放“支付宝到账100元”,我就会发放多个通知,依次播放wx-pre,1,bai,yuan这几个语音,连贯起来就能达到要求

笔者能力有限,暂时想到的方法就是这个,有好的方法可以多多分享,沟通

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352