iOS推送實(shí)現(xiàn)語音播報(bào)實(shí)踐

一寨腔、實(shí)現(xiàn)思路

1、應(yīng)用活躍時(shí)率寡,合成語音迫卢,播放語音

2、應(yīng)用被殺死冶共,喚醒應(yīng)用乾蛤,合成語音,播放語音

二捅僵、喚醒應(yīng)用

1家卖、voip push service (iOS8以上版本)

短暫?jiǎn)拘褢?yīng)用,處理輕量級(jí)業(yè)務(wù)庙楚。業(yè)務(wù)處理完成后上荡,應(yīng)用休眠。

PushKit是蘋果在iOS8之后推出的新框架馒闷,iOS10之后酪捡,蘋果更是禁止VOIP應(yīng)用在后臺(tái)使用socket長(zhǎng)鏈接,PushKit可以說是為了VOIP而生纳账,滿足實(shí)時(shí)性的同時(shí)逛薇,還能達(dá)到省電的效果,搭配蘋果自己的CallKit(大陸已被禁止)疏虫,可以呈現(xiàn)出類似原生電話通話的效果永罚。

PushKit區(qū)別與普通APNs的地方是,它不會(huì)彈出通知卧秘,而是直接喚醒你的APP呢袱,進(jìn)入回調(diào),也就是說斯议,可以在沒點(diǎn)擊APP啟動(dòng)的情況下产捞,就運(yùn)行我們自己寫的代碼,當(dāng)然哼御,推送證書和注冊(cè)坯临、回調(diào)的方法也和APNs不同焊唬,代碼注冊(cè)流程如下:

#pragma mark 注冊(cè)pushkit 和 代理方法
- (void)registPushKit{
    //注冊(cè)voip service 服務(wù)
    float version = [UIDevice currentDevice].systemVersion.floatValue;
       if (version >= 8.0) {
        PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:nil];
        pushRegistry.delegate = self;
        pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
    }
}


- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
    //服務(wù)注冊(cè)成功,獲取token
    NSString *str = [NSString stringWithFormat:@"%@",credentials.token];
    NSString * _tokenStr = [[[str stringByReplacingOccurrencesOfString:@"<" withString:@""]

                             stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
    [[NSString stringWithFormat:@"pushkit_didUpdatePushCredentials: %@", _tokenStr] saveTolog];
    NSLog(@"pushkit token %@", _tokenStr);
    //上報(bào)token
    ......
}
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
    //收到voip推送看靠,應(yīng)用喚醒赶促,
    //合成語音佑笋,播放語音
    ....
}

證書:voip service 需要在apple開發(fā)賬號(hào)中 注冊(cè)對(duì)應(yīng)的 voip service證書八秃。跟APNs證書不同昭灵,VoIP證書不區(qū)分開發(fā)和生產(chǎn)環(huán)境苛坚,VoIP證書只有一個(gè),生產(chǎn)和開發(fā)都可用同一個(gè)證書帽衙。其他步驟和注冊(cè)apns類似啼辣。此處省略相關(guān)流程协饲。

注意點(diǎn):voip service 專為 音視頻通話應(yīng)用服務(wù)粥喜,如果應(yīng)用無相關(guān)功能凸主,上架有大概率被拒。

2额湘、serivce Extension (iOS10以上版本)

iOS10添加了很多Extension,與通知相關(guān)的extension為Notification Service Extension卿吐。

我們先來了解一下Service Extension,這個(gè)東西主要是干啥的呢锋华?
主要是嗡官,讓我們?cè)谑盏竭h(yuǎn)程推送的時(shí)候<必須是遠(yuǎn)程推送>,展示之前對(duì)通知進(jìn)行修改毯焕,因?yàn)槲覀兪盏竭h(yuǎn)程推送之前會(huì)先去執(zhí)行Service Extension中的代碼衍腥。這樣就可以在收到遠(yuǎn)程推送展示之前為所欲為了。

極光的JPushExtension基于extension來統(tǒng)計(jì)推送的到達(dá)率芥丧。

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    //解析通知信息紧阔,合成語音坊罢,播放語音
    ....
    self.contentHandler(self.bestAttemptContent);
}

注意點(diǎn):extention喚起應(yīng)用的方式续担,不受官方審核限制。

播放時(shí)長(zhǎng)受限活孩,大概5秒

iOS12以上無法播放語音物遇。

三、語音生成/合成

當(dāng)收到語音信息后憾儒,如推送附帶的語音信息询兴,需要將語音信息轉(zhuǎn)成可播放的語音,大致有以下三種方式

1起趾、使用AVSpeechSynthesis框架诗舰,直接將文字轉(zhuǎn)換成語音播報(bào)


 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
 [[AVAudioSession sharedInstance] setActive:YES error:nil];
 //創(chuàng)建語音合成器
 AVSpeechSynthesizer *avSpeech = [[AVSpeechSynthesizer alloc] init];
 //實(shí)例化發(fā)聲對(duì)象
 AVSpeechUtterance *avSpeechterance = [AVSpeechUtterance speechUtteranceWithString:@"收款10元"];
 //中文發(fā)音
 AVSpeechSynthesisVoice *voiceType = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
 avSpeechterance.voice = voiceType;
 avSpeechterance.pitchMultiplier = 0.1;//聲調(diào)
 avSpeechterance.volume = 1;//音量
 avSpeechterance.rate = 0.5;//語速
 avSpeechterance.pitchMultiplier = 1.1;
 //朗讀
 [avSpeech speakUtterance:avSpeechterance];

聲音僵硬,不好聽

如果對(duì)合成音效果不滿意训裆,可以導(dǎo)入第三方語音庫進(jìn)行處理眶根。

2蜀铲、內(nèi)置語音片段,AVComposition相關(guān)類實(shí)現(xiàn)離線合成属百,播放語音

提前錄制可能要播報(bào)的內(nèi)容:

支付寶到賬记劝、 0、 1族扰、 2厌丑、 3、 4渔呵、 5怒竿、 6、 7扩氢、 8愧口、 9、 十类茂、 百耍属、 千、 萬巩检、 十萬厚骗、 百萬、 千萬兢哭、 億领舰、 元 等等

這樣的幾種錄音,然后用相關(guān)的名字命名好<相關(guān)的規(guī)則自己命名就好>迟螺。比如push過來的是內(nèi)容是 10010冲秽,那么轉(zhuǎn)化成的錄音文件名稱的數(shù)組就是
@[@"支付寶到賬",@"1",@"萬",@"0",@"1",@"十",@"元"]。然后找到這幾個(gè)文件矩父,然后按照順序拼接成一個(gè)語音文件進(jìn)行播放

- (void)syntheticSpeech
{
    /************************合成音頻并播放*****************************/
    NSMutableArray *audioAssetArray = [[NSMutableArray alloc] init];
    NSMutableArray *durationArray = [[NSMutableArray alloc] init];
    [durationArray addObject:@(0)];


    AVMutableComposition *composition = [AVMutableComposition composition];
    NSArray *fileNameArray = @[@"daozhang",@"1",@"2",@"3",@"4",@"5",@"6"];
    CMTime allTime = kCMTimeZero;
    for (NSInteger i = 0; i < fileNameArray.count; i++) {
        NSString *auidoPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@",fileNameArray[i]] ofType:@"m4a"];
        AVURLAsset *audioAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:auidoPath]];
        [audioAssetArray addObject:audioAsset];

        // 音頻軌道
        AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
        // 音頻素材軌道
        AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
        // 音頻合并 - 插入音軌文件
        [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) ofTrack:audioAssetTrack atTime:allTime error:nil];
        // 更新當(dāng)前的位置
        allTime = CMTimeAdd(allTime, audioAsset.duration);
    }

    // 合并后的文件導(dǎo)出 - `presetName`要和之后的`session.outputFileType`相對(duì)應(yīng)锉桑。
    AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
    NSString *outPutFilePath = [[self.filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"xindong.m4a"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:outPutFilePath]) {
        [[NSFileManager defaultManager] removeItemAtPath:outPutFilePath error:nil];
    }
    // 查看當(dāng)前session支持的fileType類型
    NSLog(@"---%@",[session supportedFileTypes]);
    session.outputURL = [NSURL fileURLWithPath:outPutFilePath];
    session.outputFileType = AVFileTypeAppleM4A; //與上述的`present`相對(duì)應(yīng)
    session.shouldOptimizeForNetworkUse = YES;   //優(yōu)化網(wǎng)絡(luò)
    [session exportAsynchronouslyWithCompletionHandler:^{
        if (session.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"合并成功----%@", outPutFilePath);
            NSURL *url = [NSURL fileURLWithPath:outPutFilePath];
            static SystemSoundID soundID = 0;
            AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
            AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
                NSLog(@"播放完成");
            });
        } else {
            // 其他情況, 具體請(qǐng)看這里`AVAssetExportSessionStatus`.
        }
    }];
    /************************合成音頻并播放*****************************/
}

播放語音內(nèi)容相對(duì)固定,錄音片段需提前導(dǎo)入

3窍株、在線合成

當(dāng)收到推送內(nèi)容后民轴,在線請(qǐng)求語音數(shù)據(jù),進(jìn)行播放球订。在線合成方案的效果則相對(duì)更像人聲后裸,富有感情。

請(qǐng)求耗時(shí)冒滩,可能出現(xiàn)喚醒期間無法完成的情況微驶。

四、語音播放

1开睡、notification service Extension

蘋果在iOS12.1版本以上因苹,在extension中使用 AVFoundation框架播放音頻無效较店。Notification Service Extension errors in iOS 12.1 with AVFoundation

大概的意思是大部分的擴(kuò)展應(yīng)用extensions不能使用播放音頻容燕,所以蘋果做了限制梁呈。蘋果推崇的做法是使用彈框的方式播放音頻,而且擴(kuò)展中使用background mode 模式下的play aduio蘸秘,上架也會(huì)被拒掉

當(dāng)前補(bǔ)救思路:把遠(yuǎn)程通知在擴(kuò)展里拆分成多個(gè)本地通知官卡,每個(gè)本地通知聲音是單個(gè)的音頻,順序發(fā)出醋虏。app內(nèi)預(yù)先存入大量的語音片段寻咒,擴(kuò)展中依次發(fā)送本地通知。系統(tǒng)解析本地通知颈嚼,從app內(nèi)獲取自定義聲音毛秘,組合成語音播放。

比如:“支付寶收款10元”阻课。擴(kuò)展依次發(fā)送本地通知 :通知一(聲音“支付寶”) + 通知二(聲音”10“) + 通知三 (聲音“元”)叫挟。app依次彈出3個(gè)通知聲音,組成一句播報(bào)限煞。

app內(nèi)大量的語音片段會(huì)導(dǎo)致包體過大抹恳。發(fā)送多個(gè)本地通知會(huì)導(dǎo)致手機(jī)震動(dòng)多次,且播報(bào)聲音僵硬署驻,不自然奋献。

2、當(dāng)主應(yīng)用處于后臺(tái)時(shí)旺上,AVSpeechSynthesis框架無法播放瓶蚂。可使用AVAudioSession進(jìn)行后臺(tái)播放宣吱。這種情況下窃这,可先合成語音,轉(zhuǎn)成apple支持的格式凌节,存入沙盒钦听。類似于訊飛、百度語音sdk都支持倍奢。


//合成語音,保存在沙盒中垒棋。取路徑卒煞,進(jìn)行播放
    NSString *string = [[NSBundle mainBundle] pathForResource:@"incomingCall" ofType:@"mp3"];
    NSURL *url = [NSURL fileURLWithPath:string];
    NSData *data = [NSData dataWithContentsOfFile:string];
    NSError *error = nil;
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:&error];

    AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
    AudioServicesPlayAlertSound(soundID);
    AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
        NSLog(@"播放完成");
    });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市叼架,隨后出現(xiàn)的幾起案子畔裕,更是在濱河造成了極大的恐慌衣撬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扮饶,死亡現(xiàn)場(chǎng)離奇詭異具练,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)甜无,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門扛点,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岂丘,你說我怎么就攤上這事陵究。” “怎么了奥帘?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵铜邮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我寨蹋,道長(zhǎng)松蒜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任已旧,我火速辦了婚禮牍鞠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘评姨。我一直安慰自己难述,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布吐句。 她就那樣靜靜地躺著胁后,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗦枢。 梳的紋絲不亂的頭發(fā)上攀芯,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音文虏,去河邊找鬼侣诺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛氧秘,可吹牛的內(nèi)容都是我干的年鸳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丸相,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼搔确!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤膳算,失蹤者是張志新(化名)和其女友劉穎座硕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涕蜂,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡华匾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了机隙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜘拉。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖黍瞧,靈堂內(nèi)的尸體忽然破棺而出诸尽,到底是詐尸還是另有隱情,我是刑警寧澤印颤,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布您机,位于F島的核電站,受9級(jí)特大地震影響年局,放射性物質(zhì)發(fā)生泄漏际看。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一矢否、第九天 我趴在偏房一處隱蔽的房頂上張望仲闽。 院中可真熱鬧,春花似錦僵朗、人聲如沸赖欣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顶吮。三九已至,卻和暖如春粪薛,著一層夾襖步出監(jiān)牢的瞬間悴了,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工违寿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湃交,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓藤巢,卻偏偏與公主長(zhǎng)得像搞莺,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菌瘪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容