iOS 模仿支付寶支付到賬推送,播報(bào)錢數(shù)

最近申請(qǐng)了支付寶的二維碼收錢碼领跛,其中支付寶有這么一個(gè)功能腹侣,就是叔收,別人掃描你的二維碼給你轉(zhuǎn)賬之后,收到錢會(huì)有一條語音推送傲隶,”支付寶到賬 1000萬“之類的推送消息饺律,不管你的支付寶app有沒有被殺死。

只要你的遠(yuǎn)程推送開著跺株,并且支付寶的"二維碼收錢到賬語音提醒"复濒,都打開著,就可以收到乒省。

打開方式:支付寶點(diǎn)擊右上角設(shè)置-通用-新消息通知巧颈,打開到賬提醒即可。


image.png

對(duì)支付寶進(jìn)行相關(guān)測(cè)試:

1袖扛、iOS 10以下的設(shè)備收到錢之后不管App是殺死還是壓入后臺(tái)狀態(tài)都會(huì)播報(bào)”支付寶到賬一筆”一句固定的語音
2砸泛、iOS 10以下的設(shè)備收到錢之后不管App是殺死還是壓入后臺(tái)狀態(tài),并且設(shè)備處于靜音狀態(tài)蛆封,只是會(huì)有一個(gè)推送彈框和手機(jī)振動(dòng)
3唇礁、iOS 10以上的設(shè)備,收到錢之后惨篱,不管APP是殺死還是壓入后臺(tái)狀態(tài)盏筐,不管是靜音還是非靜音狀態(tài),在收到轉(zhuǎn)賬的時(shí)候砸讳,會(huì)播報(bào)”支付寶到賬 ***** 元”
4琢融、iOS 10以上的設(shè)備,在收到語音播報(bào)的時(shí)候簿寂,按音量鍵是可以調(diào)節(jié)音量大小的

實(shí)現(xiàn)以上功能注意的點(diǎn):

iOS 10以上和iOS10以下設(shè)備實(shí)現(xiàn)方式不一樣iOS 10以上需要考慮的因素吏奸,設(shè)備是否被殺死狀態(tài),靜音非靜音狀態(tài)陶耍,音量是否可以調(diào)節(jié),是否可以播報(bào)隨機(jī)對(duì)應(yīng)的錢數(shù)
并且別人給你轉(zhuǎn)多少錢就會(huì)播報(bào)到賬多少錢她混。

iOS 10 之前系統(tǒng)實(shí)現(xiàn)方案:

能保證設(shè)備在殺死或者壓入后臺(tái)的狀況下收到信息烈钞,應(yīng)該是遠(yuǎn)程推送的功勞了。
iOS 10 之前系統(tǒng)坤按,可以借助遠(yuǎn)程推送定制鈴聲的功能來實(shí)現(xiàn)毯欣,只要在本地添加一段提前錄制好的語音,并且在推送內(nèi)容的時(shí)候?qū)ound字段臭脓,修改成語音的名稱即可

下面來看一下iOS10以上系統(tǒng)的實(shí)現(xiàn)播報(bào)自定義語音的實(shí)現(xiàn)方式:

實(shí)現(xiàn)以上功能需借助iOS 10的 NotificationServiceExtension
首先了解下常規(guī)的遠(yuǎn)程推送邏輯

image.png

iOS 10以后添加上NotificationServiceExtension之后的推送流程


image.png

可以查看相關(guān)文檔了解: NotificationServiceExtension

NotificationServiceExtension這個(gè)東西有啥作用呢酗钞?

主要是能夠在展示推送內(nèi)容之前先獲取到相關(guān)的推送信息,可以更改或者替換相關(guān)的推送內(nèi)容。

怎么實(shí)現(xiàn)呢砚作?
不用自己創(chuàng)建UNNotificationServiceExtension類窘奏,使用Xcode提供的模板直接選擇就可以。如果你的app收到遠(yuǎn)程推送的話就會(huì)加載擴(kuò)展并且調(diào)用didReceiveNotificationRequest:withContentHandler:葫录,而做這些的前提就是着裹,遠(yuǎn)程推送的那一套配置<證書之類的>得做好,還有就是推送的字典中包含mutable-content并且它的值是1米同;

image.png

iOS 10系統(tǒng)之后遠(yuǎn)程推送播報(bào)語音的實(shí)現(xiàn)思路:

設(shè)備在收到遠(yuǎn)程推送的時(shí)候骇扇,進(jìn)入Service Extension,將遠(yuǎn)程推送的信息攔截下來面粮,將需要播報(bào)的文字信息通過相關(guān)方式翻譯成語音少孝,進(jìn)行播報(bào),播報(bào)完畢之后再展示彈框信息熬苍。

UNNotificationServiceExtension的功能就是可以攔截到遠(yuǎn)程推送的信息稍走,然后當(dāng)調(diào)用self.contentHandler(self.bestAttemptContent); 之后就會(huì)進(jìn)行彈框顯示了,如果進(jìn)行了彈框顯示冷溃,那么UNNotificationServiceExtension的使命意味著結(jié)束了钱磅。

當(dāng)攔截到信息,到彈框最多有30秒的時(shí)間進(jìn)行操作似枕。
Your extension has a limited amount of time (no more than 30 seconds) to modify the content and execute the contentHandler
block. If you do not execute that block in a timely manner, the system calls your extension’s serviceExtensionTimeWillExpire method to give you one last chance to execute the block. If you do not, the system presents the notification’s original content to the user.

image.png

大致思路定下之后盖淡,下一步操作,怎么把文字翻譯成語音進(jìn)行播報(bào)

1凿歼、使用科大訊飛以及類似的三方庫(kù)褪迟,將遠(yuǎn)程推送過來的文字,直接使用三方庫(kù)播放出來
2答憔、使用 AVSpeechSynthesisVoice 相關(guān)類將遠(yuǎn)程推送過來的文字直接轉(zhuǎn)化成語音味赃,進(jìn)行播報(bào)
3、如果播報(bào)的是錢數(shù)的話虐拓,可以在本地將相關(guān)可能播報(bào)的語音片段錄制好心俗,然后根據(jù)推送過來的內(nèi)容標(biāo)識(shí),對(duì)語音片段進(jìn)行拼接蓉驹,然后進(jìn)行播放

如果對(duì)Notification Servivice Extension不是很熟悉的城榛,建議先了解一下
iOS10 推送extension之 Service Extension你玩過了嗎?


在介紹相關(guān)方式之前态兴,先介紹一個(gè)測(cè)試工具
SmartPush

使用方式也很簡(jiǎn)單狠持,運(yùn)行起來,輸入相關(guān)的推送數(shù)據(jù)和token瞻润,并且選擇對(duì)應(yīng)的推送證書喘垂,點(diǎn)擊推送即可


image.png

正式進(jìn)入主題

推送的內(nèi)容是:

{
  "aps":{
    "alert":{
      "title":"iOS 10 title",
      "subtitle":"iOS 10 subtitle",
"body":"世上只有媽媽好甜刻,有媽的孩子像塊寶。投進(jìn)媽媽的懷抱正勒,幸福哪里找得院。沒媽的孩子像根草。大河向東流昭齐,天上的星星參北斗尿招,嘿呀,咿兒呀阱驾,嘿  嘿  咿兒呀"
    },
    "my-attachment":"http://img01.taopic.com/160317/240440-16031FU23937.jpg",
    "mutable-content":1,
    "category":"myNotificationCategory1",
    "badge":3
    
  }
}

1就谜、使用科大訊飛以及類似的三方庫(kù),將遠(yuǎn)程推送過來的文字里覆,直接使用三方庫(kù)播放出來

這個(gè)流量用多了應(yīng)該收費(fèi)丧荐,具體可查看科大訊飛官網(wǎng)

將推送過來的文字轉(zhuǎn)化成語音播放,然后在播放完畢的回調(diào)中執(zhí)行self.contentHandler(self.bestAttemptContent);

@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
    AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@property (nonatomic, strong)AVAudioPlayer *myPlayer;

@property (nonatomic, strong) NSString *filePath;

// AVSpeechSynthesisVoice 播放完畢之后的回調(diào)block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;

// 科大訊飛播放完畢之后的block回調(diào)
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;

// 語音合成完畢之后喧枷,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    __weak __typeof(self)weakSelf = self;
    
    /**************************************************************************/
    
    
    // 方式1虹统,直接使用科大訊飛播放,成功隧甚,但是剛開始的時(shí)候可能需要幾秒的準(zhǔn)備播放時(shí)間
    [self playVoiceKeDaXunFeiWithMessage:self.bestAttemptContent.body withBlock:^{
        weakSelf.contentHandler(weakSelf.bestAttemptContent);
    }];
}
#pragma mark- 使用科大訊飛播放語音
- (void)playVoiceKeDaXunFeiWithMessage:(NSString *)message withBlock:(PlayVoiceBlock)finshBlock
{
    if (finshBlock) {
        self.kedaFinshBlock = finshBlock;
    }
    
    //創(chuàng)建語音配置,appid必須要傳入车荔,僅執(zhí)行一次則可
    NSString *initString = [[NSString alloc] initWithFormat:@"appid=%@",@"59db7ce2"];
    
    //所有服務(wù)啟動(dòng)前,需要確保執(zhí)行createUtility
    [IFlySpeechUtility createUtility:initString];
    
    /******************************************************/
    //獲取語音合成單例
    _iFlySpeechSynthesizer = [IFlySpeechSynthesizer sharedInstance];
    //設(shè)置協(xié)議委托對(duì)象
    _iFlySpeechSynthesizer.delegate = self;
    //設(shè)置合成參數(shù)
    //設(shè)置在線工作方式
    [_iFlySpeechSynthesizer setParameter:[IFlySpeechConstant TYPE_CLOUD]
                                  forKey:[IFlySpeechConstant ENGINE_TYPE]];
    //設(shè)置音量戚扳,取值范圍 0~100
    [_iFlySpeechSynthesizer setParameter:@"50"
                                  forKey: [IFlySpeechConstant VOLUME]];
    //發(fā)音人忧便,默認(rèn)為”xiaoyan”,可以設(shè)置的參數(shù)列表可參考“合成發(fā)音人列表”
    [_iFlySpeechSynthesizer setParameter:@" xiaoyan "
                                  forKey: [IFlySpeechConstant VOICE_NAME]];
    //保存合成文件名帽借,如不再需要珠增,設(shè)置為nil或者為空表示取消,默認(rèn)目錄位于library/cache下
    [_iFlySpeechSynthesizer setParameter:@" tts.pcm"
                                  forKey: [IFlySpeechConstant TTS_AUDIO_PATH]];
    //啟動(dòng)合成會(huì)話
    [_iFlySpeechSynthesizer startSpeaking:message];
    
}

//IFlySpeechSynthesizerDelegate協(xié)議實(shí)現(xiàn)
//合成結(jié)束
- (void) onCompleted:(IFlySpeechError *) error {
    
    NSLog(@"合成結(jié)束 error ===== %@",error);
    self.kedaFinshBlock();
}

結(jié)果
滿足以下兩點(diǎn)要求(1)砍艾、iOS 10以上的設(shè)備蒂教,收到推送之后,不管APP是殺死還是壓入后臺(tái)狀態(tài)脆荷,不管是靜音還是非靜音狀態(tài)凝垛,在收到轉(zhuǎn)賬的時(shí)候,會(huì)播報(bào)”到賬 ***** 元”
(2)蜓谋、iOS 10以上的設(shè)備梦皮,在收到語音播報(bào)的時(shí)候,按音量鍵是可以調(diào)節(jié)音量大小的

坑點(diǎn)
說明:當(dāng)前實(shí)現(xiàn)的是將push內(nèi)容中的body播放出來
1孤澎、如果你收到推送了但是添加了系統(tǒng)的鈴聲,也就是你在push的json中添加了"sound":"default"那么就可能會(huì)影響推送聲音的播放
2欠窒、推送有問題

image.png

3覆旭、播放的語音時(shí)長(zhǎng)最好不要超過30秒
4退子、可能你導(dǎo)入了三方的播放SDK,但是在service無法使用型将,導(dǎo)入都報(bào)錯(cuò)
注意:
選擇引入的時(shí)候一定要勾上對(duì)應(yīng)的targets


image.png

查看導(dǎo)入后的結(jié)果


image.png

4寂祥、如果說你的遠(yuǎn)程推送還是走的iOS10之前的邏輯,那么請(qǐng)檢查一下你的推送的json有沒有【"mutable-content":1】


2七兜、使用 AVSpeechSynthesisVoice 相關(guān)類將遠(yuǎn)程推送過來的文字直接轉(zhuǎn)化成語音丸凭,進(jìn)行播報(bào)
AVSpeechSynthesisVoice 可查看官方文檔

流程:
將推送過來的文字轉(zhuǎn)化成語音播放,然后在播放完畢的回調(diào)中執(zhí)行腕铸,和科大訊飛類似惜犀,不過是蘋果系統(tǒng)相關(guān)類

相關(guān)代碼

@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
    AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@property (nonatomic, strong)AVAudioPlayer *myPlayer;

@property (nonatomic, strong) NSString *filePath;

// AVSpeechSynthesisVoice 播放完畢之后的回調(diào)block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;

// 科大訊飛播放完畢之后的block回調(diào)
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;

// 語音合成完畢之后,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    __weak __typeof(self)weakSelf = self;
    
   
    /**************************************************************************/
    
//  方式4狠裹,AVSpeechSynthesisVoice使用系統(tǒng)方法虽界,文字轉(zhuǎn)語音播報(bào),成功
    [self playVoiceWithAVSpeechSynthesisVoiceWithContent:self.bestAttemptContent.body fishBlock:^{
        weakSelf.contentHandler(weakSelf.bestAttemptContent);
    }];
    
}
#pragma mark- AVSpeechSynthesisVoice文字轉(zhuǎn)語音進(jìn)行播放,成功
- (void)playVoiceWithAVSpeechSynthesisVoiceWithContent:(NSString *)content fishBlock:(PlayVoiceBlock)finshBlock
{
    if (content.length == 0) {
        return;
    }
    if (finshBlock) {
        self.finshBlock = finshBlock;
    }
    
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    
    // 創(chuàng)建嗓音涛菠,指定嗓音不存在則返回nil
    AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
    
    // 創(chuàng)建語音合成器
    synthesizer = [[AVSpeechSynthesizer alloc] init];
    synthesizer.delegate = self;
    
    // 實(shí)例化發(fā)聲的對(duì)象
    AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:content];
    utterance.voice = voice;
    utterance.rate = 0.5; // 語速
    
    // 朗讀的內(nèi)容
    [synthesizer speakUtterance:utterance];
}

- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance
{
    NSLog(@"開始");
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance
{
    self.finshBlock();
    NSLog(@"結(jié)束");
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance
{
    
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didContinueSpeechUtterance:(AVSpeechUtterance *)utterance
{
    
}
- (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance
{
    
}

坑點(diǎn)
說明:當(dāng)前實(shí)現(xiàn)的是將push內(nèi)容中的body播放出來
1莉御、如果你收到推送了但是添加了系統(tǒng)的鈴聲,也就是你在push的json中添加了"sound":"default"那么就可能會(huì)影響推送聲音的播放
2俗冻、播放的語音時(shí)長(zhǎng)最好不要超過30秒
3礁叔、如果說你的遠(yuǎn)程推送還是走的iOS10之前的邏輯,那么請(qǐng)檢查一下你的推送的json有沒有【"mutable-content":1】
4迄薄、如果在手機(jī)靜音的狀態(tài)下聽不到播報(bào)的語音
5琅关、AVSpeechSynthesizer 的對(duì)象一定要設(shè)置成全局變量,不然代理不會(huì)執(zhí)行噪奄。那么你播放完成的回調(diào)就不會(huì)起作用
添加設(shè)置

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];

結(jié)果
滿足以下兩點(diǎn)要求
(1)死姚、iOS 10以上的設(shè)備,收到推送之后勤篮,不管APP是殺死還是壓入后臺(tái)狀態(tài)都毒,不管是靜音還是非靜音狀態(tài),在收到轉(zhuǎn)賬的時(shí)候碰缔,會(huì)播報(bào)”到賬 ***** 元”
(2)账劲、iOS 10以上的設(shè)備,在收到語音播報(bào)的時(shí)候金抡,按音量鍵是可以調(diào)節(jié)音量大小的


3瀑焦、如果播報(bào)的是錢數(shù)的話,可以在本地將相關(guān)可能播報(bào)的語音片段錄制好梗肝,然后根據(jù)推送過來的內(nèi)容標(biāo)識(shí)榛瓮,對(duì)語音片段進(jìn)行拼接,然后進(jìn)行播放

流程:
如果播報(bào)的內(nèi)容是相對(duì)固定的片段組合體巫击,這里那支付寶舉例禀晓。
比如提前先錄好 以下可能播報(bào)的內(nèi)容
*****到賬精续、 0、 1粹懒、 2重付、 3、 4凫乖、 5确垫、 6、 7帽芽、 8删掀、 9、 十嚣镜、 百爬迟、 千、 萬菊匿、 十萬付呕、 百萬、 千萬跌捆、 億徽职、 元 等等
然后根據(jù)推送的內(nèi)容進(jìn)行相關(guān)語音文件的對(duì)應(yīng),然后拼接佩厚,拼接完畢之后生成一個(gè)語音文件姆钉,然后進(jìn)行播放

相關(guān)代碼:

@interface NotificationService ()<IFlySpeechSynthesizerDelegate,AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
    AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) IFlySpeechSynthesizer *iFlySpeechSynthesizer;

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@property (nonatomic, strong)AVAudioPlayer *myPlayer;

@property (nonatomic, strong) NSString *filePath;

// AVSpeechSynthesisVoice 播放完畢之后的回調(diào)block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;

// 科大訊飛播放完畢之后的block回調(diào)
@property (nonatomic, copy)PlayVoiceBlock kedaFinshBlock;

// 語音合成完畢之后,使用 AVAudioPlayer 播放
@property (nonatomic, copy)PlayVoiceBlock aVAudioPlayerFinshBlock;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    __weak __typeof(self)weakSelf = self;
    
   
    /*******************************推薦用法*******************************************/
    
    // 方法3,語音合成抄瓦,使用AVAudioPlayer播放,成功
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];

    [self hechengVoiceAVAudioPlayerWithFinshBlock:^{
        weakSelf.contentHandler(weakSelf.bestAttemptContent);
    }];   
}
#pragma mark- 合成音頻使用 AVAudioPlayer 播放
- (void)hechengVoiceAVAudioPlayerWithFinshBlock:(PlayVoiceBlock )block
{
    if (block) {
        self.aVAudioPlayerFinshBlock = block;
    }
    
    /************************合成音頻并播放*****************************/

    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]];
        
        // 音頻軌道
        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];
            
            self.myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
            
            self.myPlayer.delegate = self;
            [self.myPlayer play];
            
            
        } else {
            // 其他情況, 具體請(qǐng)看這里`AVAssetExportSessionStatus`.
            // 播放失敗
            self.aVAudioPlayerFinshBlock();
        }
    }];
    
    /************************合成音頻并播放*****************************/
}
#pragma mark- AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
    if (self.aVAudioPlayerFinshBlock) {
        self.aVAudioPlayerFinshBlock();
    }
}

結(jié)果
(1)、iOS 10以上的設(shè)備钙姊,收到推送之后毯辅,不管APP是殺死還是壓入后臺(tái)狀態(tài),不管是靜音還是非靜音狀態(tài)煞额,在收到轉(zhuǎn)賬的時(shí)候思恐,會(huì)播報(bào)”到賬 ***** 元”
(2)、iOS 10以上的設(shè)備膊毁,在收到語音播報(bào)的時(shí)候胀莹,按音量鍵是可以調(diào)節(jié)音量大小的

坑點(diǎn)
(1)、注意上面的坑點(diǎn)
(2)婚温、播放音頻的時(shí)候有兩種播放形式AudioServicesPlayAlertSoundWithCompletionAVAudioPlayer描焰。
建議使用AVAudioPlayer,因?yàn)?em>AVAudioPlayer能滿足不受設(shè)備靜音不靜音的影響栅螟,能根據(jù)音量調(diào)節(jié)聲音的高低荆秦。
AudioServicesPlayAlertSoundWithCompletion局限性比較大逆日,會(huì)受靜音的影響,只會(huì)震動(dòng)萄凤,并且無法調(diào)整音量的高低。

合成語音之后如果使用AudioServicesCreateSystemSoundID播放的話搪哪,有一定的局限性
http://www.hangge.com/blog/cache/detail_771.html
1靡努,系統(tǒng)聲音服務(wù)介紹:
系統(tǒng)聲音服務(wù)提供了一個(gè)Api,用于播放不超過30秒的聲音晓折。它支持的文件格式有限惑朦,具體的說只有CAF、AIF和使用PCM或IMA/ADPCM數(shù)據(jù)的WAV文件漓概。
但此函數(shù)沒有提供操作聲音和控制音量的功能漾月,因此如果是要為多媒體或游戲創(chuàng)建專門聲音,就不要使用系統(tǒng)聲音服務(wù)胃珍。

2梁肿,系統(tǒng)聲音服務(wù)支持如下三種類型:
(1)聲音:立刻播放一個(gè)簡(jiǎn)單的聲音文件。如果手機(jī)靜音觅彰,則用戶什么也聽不見吩蔑。
(2)提醒:播放一個(gè)聲音文件,如果手機(jī)設(shè)為靜音或震動(dòng)填抬,則通過震動(dòng)提醒用戶烛芬。
(3)震動(dòng):震動(dòng)手機(jī),而不考慮其他設(shè)置飒责。


說明:
上面并沒有實(shí)現(xiàn) 數(shù)字轉(zhuǎn)對(duì)應(yīng)音頻文件名稱數(shù)組的過程赘娄,直接實(shí)現(xiàn)的是合成音頻的方法。

Extension的運(yùn)行生命周期:
iOS對(duì)于擴(kuò)展的支持已經(jīng)由最初的6類到了如今iOS10的19類(相信隨著iOS的發(fā)展擴(kuò)展的覆蓋面也會(huì)越來越廣)宏蛉,當(dāng)然不同類型的擴(kuò)展其用途和用法均不盡相同遣臼,但是其工作原理和開發(fā)方式是類似的。下面列出擴(kuò)展的幾個(gè)共同點(diǎn):

擴(kuò)展依附于應(yīng)用而不能單獨(dú)發(fā)布和部署檐晕;
擴(kuò)展和包含擴(kuò)展的應(yīng)用(containing app)生命周期是獨(dú)立的暑诸,分別運(yùn)行在兩個(gè)不同的進(jìn)程中;
擴(kuò)展的運(yùn)行依賴于宿主應(yīng)用(或者叫載體應(yīng)用 host app辟灰,而不是containing app)其生命周期由宿主應(yīng)用確定个榕;
對(duì)開發(fā)者而言擴(kuò)展作為一個(gè)單獨(dú)的target而存在;
擴(kuò)展通常展現(xiàn)在系統(tǒng)UI或者其他應(yīng)用中芥喇,運(yùn)行應(yīng)該盡可能的迅速而功能單一西采;


image.png

關(guān)于斷點(diǎn)調(diào)試
如果想通過斷點(diǎn)調(diào)試來了解或者檢查推送的流程怎么搞呢?

方法一:

image.png

然后在相關(guān)的target中打斷點(diǎn)就可以了继控,但是貌似在Xcode 9.0 9.1上面選擇service Extension不太管用械馆,直接走iOS 10 之前的推送去了胖眷,不知道為啥。


image.png

調(diào)試 content Extension


image.png

方法二:
如果想同時(shí)調(diào)試各個(gè)target怎么辦霹崎?

將項(xiàng)目運(yùn)行起來珊搀,然后發(fā)送一條推送之后,激活Service Extension尾菇,如果有需要可以激活content Extension<下拉一下推送條查看就好境析,前提你的conten Extension可以使用>
然后去選擇,這個(gè)時(shí)候不要stop掉程序派诬,根據(jù)下圖選擇完畢之后劳淆,在相關(guān)的地方打上斷點(diǎn),再次推送默赂。


image.png
image.png

建議使用方法二沛鸵,各個(gè)流程順序更加直觀

關(guān)于數(shù)據(jù)共享
如果有這么一個(gè)需求,在主工程中有這么一個(gè)按鈕缆八,讓用戶控制是否播放錢數(shù)曲掰,還是受到一條簡(jiǎn)單的到賬通知。
思路可以是這樣的奈辰,在主工程中根絕用戶的操作添加一個(gè)標(biāo)識(shí)蜈缤,然后在Extension中獲取這個(gè)標(biāo)識(shí),通過這個(gè)標(biāo)識(shí)的狀態(tài)來判斷是否需要讀取用戶的到賬通知冯挎。
這個(gè)就是關(guān)于主工程和Extension之間數(shù)據(jù)共享的問題了底哥。
詳細(xì)可參考:

Extension 與主app共享數(shù)據(jù)

經(jīng)測(cè)試,可以很輕松實(shí)現(xiàn)

image.png
image.png
image.png
image.png
image.png
image.png

最后獻(xiàn)上相關(guān)的Demo地址房官,如果你有更好的建議歡迎留言趾徽,如有不正,歡迎來噴翰守。

可以直接用我的Demo進(jìn)行調(diào)試孵奶,調(diào)試的時(shí)候注意修改下bundleId,然后用自己的開發(fā)者賬號(hào)配置一下相關(guān)的push證書就可以了

image.png
image.png

如果最終修改我的項(xiàng)目還是不能收到推送蜡峰,那么建議你重新生成一個(gè)新的項(xiàng)目然后重新配置相關(guān)證書即可了袁。
如果有幫到你,記得點(diǎn)贊奧湿颅,如果你有更好的方案载绿,歡迎交流溝通。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末油航,一起剝皮案震驚了整個(gè)濱河市崭庸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖怕享,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件执赡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡函筋,警方通過查閱死者的電腦和手機(jī)沙合,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跌帐,“玉大人灌诅,你說我怎么就攤上這事『” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵即舌,是天一觀的道長(zhǎng)佣盒。 經(jīng)常有香客問我,道長(zhǎng)顽聂,這世上最難降的妖魔是什么肥惭? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮紊搪,結(jié)果婚禮上蜜葱,老公的妹妹穿的比我還像新娘。我一直安慰自己耀石,他們只是感情好牵囤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滞伟,像睡著了一般揭鳞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梆奈,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天野崇,我揣著相機(jī)與錄音,去河邊找鬼亩钟。 笑死乓梨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的清酥。 我是一名探鬼主播扶镀,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼焰轻!你這毒婦竟也來了狈惫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胧谈,沒想到半個(gè)月后忆肾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菱肖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年客冈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稳强。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡场仲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出退疫,到底是詐尸還是另有隱情渠缕,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布褒繁,位于F島的核電站亦鳞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏棒坏。R本人自食惡果不足惜燕差,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坝冕。 院中可真熱鬧徒探,春花似錦、人聲如沸喂窟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磨澡。三九已至偷溺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钱贯,已是汗流浹背挫掏。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秩命,地道東北人尉共。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像弃锐,于是被迫代替她去往敵國(guó)和親袄友。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)霹菊、插件剧蚣、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,033評(píng)論 4 62
  • 推送通知 注意:這里說的推送通知跟NSNotification有所區(qū)別 NSNotification是抽象的支竹,不可...
    iOS開發(fā)攻城獅閱讀 4,191評(píng)論 1 13
  • 在公司成天跟人勾心斗角,你當(dāng)你演宮心計(jì)了吧鸠按? 銷售一個(gè)個(gè)都跟大爺似的礼搁,牛氣沖天。讓同事幫忙干活就是我一說你就該懂目尖,...
    書宸閱讀 237評(píng)論 0 0
  • “美女馒吴,需要買手機(jī)嗎?” 深圳華強(qiáng)北數(shù)碼街瑟曲,一20歲出頭的男子攔住路過的女子饮戳,異常熱情問道。 “我就隨便看看洞拨〕豆蓿” ...
    太白雜談閱讀 421評(píng)論 0 0
  • 我是一個(gè)懶人,從小缺乏下廚的鍛煉烦衣,婚后有能干的老公歹河,還有不放手的公公婆婆爸爸媽媽,所以根本輪不到我出手琉挖。但是群里的...
    諸慧的身心園地閱讀 464評(píng)論 9 10