??上個(gè)版本的開(kāi)發(fā)計(jì)劃中產(chǎn)品同學(xué)建議在我們的商家版App中做后臺(tái)語(yǔ)音播報(bào)功能,在評(píng)審的時(shí)候我就在想,完全可以通過(guò)Push靜默推送來(lái)實(shí)現(xiàn)后臺(tái)播放音頻來(lái)實(shí)現(xiàn)(后續(xù)事實(shí)證明,這是個(gè)大坑)。
??關(guān)于靜默推送 推薦大家看一下http://www.itdecent.cn/p/c211bd295d58
??好了,最后通過(guò)push和backGroundModes實(shí)現(xiàn)了在后臺(tái)語(yǔ)言播放的功能,工程的配置:

??關(guān)于
BackGroundModes推薦大家看一下 http://www.itdecent.cn/p/121fc5b7f2d3??測(cè)試環(huán)境一切正常,testflight也一切正常,然后提交審核,然后就悲劇了。被拒的原因的大概意思就是你開(kāi)啟了后臺(tái)掛起播放音頻的功能需要演示視頻。
??然后我周末跑去公司錄制了視頻上傳到Y(jié)ouTuBe,為了安心我還上傳到了優(yōu)酷,兩個(gè)鏈接扔上去,提交審核,這些穩(wěn)了吧。然后又被拒了。

??然后被拒原因大概是我還是看不到你哪里用了后臺(tái)播放音頻呀。

??然后我就懵逼了,查了一天的資料,終于大概理解了蘋(píng)果審核人員的意思,靜默推送蘋(píng)果爸爸的初衷是用了在后臺(tái)的時(shí)候處理數(shù)據(jù)刷新相關(guān)的,靜默、靜默的意思就是不打擾用戶,然后我用它來(lái)做語(yǔ)音播報(bào)顯然是違背了靜默推送的初衷,然后只能換思路了。
??在iOS 10
UNNotificationServiceExtension 剛出現(xiàn)的時(shí)候了解過(guò),然后在后續(xù)沒(méi)有用到就忘的差不多了,在對(duì)它進(jìn)一步了解之后,我發(fā)現(xiàn)它完全能夠在不開(kāi)啟后臺(tái)運(yùn)行相關(guān)功能的情況下來(lái)實(shí)現(xiàn)語(yǔ)音播報(bào)功能,好吧,開(kāi)始動(dòng)手:??在你的功能首先創(chuàng)建
UNNotificationServiceExtension target
Service Extension的Bundle Identifier不能和Main Target(也就是你自己的App Target)的Bundle Identifier相同,否則會(huì)報(bào)BundeID重復(fù)的錯(cuò)誤。Service Extension的Bundle Identifier需要在Main Target的命名空間下,比如說(shuō)Main Target的BundleID為io.jpush.xxx,那么
Service Extension的BundleID應(yīng)該類似與io.jpush.xxx.yyy這樣的格式。如果不這么做,你可能會(huì)遇到一個(gè)錯(cuò)誤。??創(chuàng)建好了把相關(guān)音頻文件導(dǎo)入:

在下面方法做相關(guān)數(shù)據(jù)處理
- (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];
[[AVAudioSession sharedInstance] setActive:YES error:NULL];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
NSDictionary *userInfo = self.bestAttemptContent.userInfo;
NSDictionary * aps = [userInfo objectForKey:@"aps"];
NSString * soundCommand = [aps valueForKey:@"soundCommand"];
[self playSoundsWithSoundCommand:soundCommand];
self.contentHandler(self.bestAttemptContent);
}
/*播放*/
-(void)playWithFileUrlString:(NSString *)fileURLString{
if (![fileURLString length]) {
return;
}
AVAudioSession * session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
BOOL ret = [session setCategory:AVAudioSessionCategoryPlayback error:nil];
NSLog(@"%d",ret);
NSURL *fileURL = [[NSBundle mainBundle]URLForResource:fileURLString withExtension:@".mp3"];
static SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(fileURL), &soundID);
AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
NSLog(@"播放完成");
});
}
??記住了在做完相關(guān)操作之后再調(diào)用self.contentHandler(self.bestAttemptContent);方法 進(jìn)入墓碑模式(不執(zhí)行應(yīng)用程序的任何代碼)
??還有很重要的一點(diǎn),記住push的試試讓后臺(tái)同學(xué)要加入一個(gè)參數(shù)"mutable-content" = 1;,不然我們的擴(kuò)展類方法是攔截不到推送的哦,要和alert 同級(jí)的,位置不要錯(cuò)。

??接下來(lái)運(yùn)行-測(cè)試,完美實(shí)現(xiàn)。打包,然后又報(bào)錯(cuò)了,看了原因是因?yàn)閿U(kuò)展target和我的原來(lái)工程的簽名不是同一team,這時(shí)候就要用的appid創(chuàng)建的時(shí)候創(chuàng)建一個(gè)通配符appid了。
??在你開(kāi)發(fā)者中心創(chuàng)建一個(gè)通配符appid包含到你的擴(kuò)展應(yīng)用下,然后生成相關(guān)開(kāi)發(fā)和生成Profile文件,下載下來(lái),然后打包。成功!??!
??接下來(lái)就是等待蘋(píng)果爸爸的審核了,不過(guò)十拿九穩(wěn)啦~
??參考文檔:http://www.itdecent.cn/p/db9c4aec2b93
重要補(bǔ)充:
??在iOS12.1之后蘋(píng)果爸爸禁用了UNNotificationServiceExtension 在后臺(tái)使用AVAudioSession播放語(yǔ)音文件,解決方法是我們拿到- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler方法里面的bestAttemptContent給他的sound賦值一下就OK了。
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.mp3",fileURLString]];
??這里提一下如果用這種方法播放語(yǔ)音文件相應(yīng)的語(yǔ)音資源需要導(dǎo)入到主程序中,不能放在UNNotificationServiceExtension中。
??再次補(bǔ)充這種方法只能在后臺(tái)播放語(yǔ)音,前臺(tái)播放需要另外到主程序中去實(shí)現(xiàn)。