專案需求:
近期專案有個需求,實現類似支付寶微信收款後的語音播報如:支付寶到賬xx元。要求是APP在前臺執行、鎖屏、殺死程序後都會有語音播報。
預想方案:
1.通過UIBackgroundTaskIdentifier不斷向程式索要處理時間(這種方案不知道以前可行,現在好像是最多隻能保持3分鐘的時間,一般30s左右)-fail
2.後臺播放無聲音,保持APP一直執行,但是上架APPStore一般不是音樂類的都無法過審--fail
因此,我們現在選擇了遠端推播實現需求:(附DEMO)
3.通過遠端推播,在iOS10的時候,釋出了UNNotificationServiceExtension擴充套件,關於此擴充套件,可以網上選擇一些資料iOS10 推播extension之 Service Extension,主要的核心思想就是,在遠端推播到底裝置之前,給你一個修改的機會,我們知道,推播體是有限制的,而且推播體大小也會影響推播的效率,藉助這個,我們可以修改標題、內容,也可以從網路上請求到內容,再去合成一個新的推播。
接下來就是實現手機接收到通知之後播報語音了,關於這個功能的實現在iOS10以後蘋果新增了「推播拓展」UNNotificationServiceExtension
,我們可以在這裡操作,在這裡我用的是蘋果官方的AVSpeechSynthesizer
和AVSpeechUtterance
來將接收到的推播內容轉換成語音播報
貌似沒啥問題,但是iOS12.1以後,不在允許在UNNotificationServiceExtension
中播放語音了,只有系統提示音,阿歐。。。心好累。。。,沒辦法只好先在想辦法,上網查詢資料發現前輩們果然有解決辦法,哈哈。。。
1.設定遠端推播
2.在收到遠端推播時,呼叫本地推播
3.把播報金額拆分成,一、二、三,四、五...千、百、萬、點、元等一個個音訊檔,根據推播過來的金額進行進行篩選然後按照順序放入陣列,具體的在下面有介紹(caculateNumber方法處理)
4.迴圈(遞迴)傳送本地推播播放專案中的音樂檔案
重點:
功能實現
1.設定 UNNotificationServiceExtension
具體的設定可參考檔案:iOS10 推播extension之 Service Extension你玩過了嗎?,這裡不在囉嗦了
2.設定好之後,然後我們在AppDelegate裡面進行通知的註冊,核心程式碼
// 註冊推播
- (void)registerRemoteNotification{
UIApplication *application = [UIApplication sharedApplication];
application.applicationIconBadgeNumber = 0;
if([application respondsToSelector:@selector(registerUserNotificationSettings:)])
{
UIUserNotificationType notificationTypes = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
[application registerUserNotificationSettings:settings];
}
#if !TARGET_IPHONE_SIMULATOR
//iOS8 註冊APNS
if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) {
[application registerForRemoteNotifications];
}else{
UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert;
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes];
}
#endif
}
3.然後我們在NotificationService.m檔案內寫入收到json資料解析,獲取推播金額並處理,得到語音檔案的陣列,並播放語音(本地推播 -音訊)檔案
程式碼如下:
#import "NotificationService.h"
#import "XSAudioManager.h"
#import <AVFoundation/AVFoundation.h>
#define kFileManager [NSFileManager defaultManager]
typedef void(^PlayVoiceBlock)(void);
@interface NotificationService ()<AVAudioPlayerDelegate,AVSpeechSynthesizerDelegate>
{
AVSpeechSynthesizer *synthesizer;
}
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
// AVSpeechSynthesisVoice 播放完畢之後的回撥block
@property (nonatomic, copy)PlayVoiceBlock finshBlock;
//聲音檔案的播放器
@property (nonatomic, strong)AVAudioPlayer *myPlayer;
//聲音檔案的路徑
@property (nonatomic, strong) NSString *filePath;
@end
@implementation NotificationService
/*
*後臺推播的json案例
{"aps":{"alert":"錢到啦收款10000元","badge":1,"mutable-content":1,"amount":10000, "sound":"default"}}
*/
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
//step1: 推播json解析,獲取推播金額
NSMutableDictionary *dict = [self.bestAttemptContent.userInfo mutableCopy] ;
NSDictionary *extras = [dict objectForKey:@"aps"] ;
BOOL playaudio = [[extras objectForKey:@"amount"] boolValue] ;
if(playaudio) {
//step2:先處理金額,得到語音檔案的陣列,並播放語音(本地推播 -音訊)
NSString *amount = [extras objectForKey:@"amount"] ;//10000
NSArray *musicArr = [[XSAudioManager sharedInstance] getMusicArrayWithNum:amount];
__weak __typeof(self)weakSelf = self;
[[XSAudioManager sharedInstance] pushLocalNotificationToApp:0 withArray:musicArr completed:^{
// 播放完成後,通知系統
weakSelf.contentHandler(weakSelf.bestAttemptContent);
}];
} else {
//系統通知
self.contentHandler(self.bestAttemptContent);
}
}
// 30s的處理時間即將結束時,該方法會被呼叫,最後一次提醒使用者去做處理
- (void)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.
self.contentHandler(self.bestAttemptContent);
}
@end
我們定義了一個聲音處理的中間類XSAudioManager,因為擴充套件和APP本身都會使用這個類,所以新建這個檔案的時候,注意勾選Targets
XSAudioManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// 處理完成的callback
typedef void (^XSNotificationPushCompleted)(void) ;
@interface XSAudioManager : NSObject
+ (instancetype)sharedInstance;
//先處理金額,得到語音檔案的陣列
-(NSArray *)getMusicArrayWithNum:(NSString *)numStr;
//迴圈呼叫本地通知,播放音訊檔
-(void)pushLocalNotificationToApp:(NSInteger)index withArray:(NSArray *)tmparray completed:(XSNotificationPushCompleted)completed;
/*
**系統的語音播報(紅包訊息)
*AVSpeechSynthesizer(iOS10.0-12.0),之後不支援播報
*/
- (void)speechWalllentMessage:(NSString *)numStr;
@end
NS_ASSUME_NONNULL_END
4.APP在前臺的時候是沒有聲音的,前臺需要走正常的推播解析,走正常的語音播報,呼叫系統的播報方法。
//語音播報紅包訊息
- (void)speechWalllentMessage:(NSString *)numStr {
//播放語音
// 合成器 控制播放,暫停
AVSpeechSynthesizer *_synthesizer;
// 範例化說話的語言,說中文、英文
AVSpeechSynthesisVoice *_voice;
_voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh_CN"];
// 要朗誦,需要一個語音合成器
_synthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:[NSString stringWithFormat:@"XX到賬%@元",numStr]];
//指定語音,和朗誦速度
utterance.voice = _voice;
// utterance.rate = AVSpeechUtteranceDefaultSpeechRate;
utterance.rate = 0.55;
utterance.pitchMultiplier = 1.0f; //改變音調
// utterance.volume = 1;
//啟動
[_synthesizer speakUtterance:utterance];
}
至此,語音播報算是完成了。
在iOS12.1以上,遠端推播+本地推播,前臺可以正常播放,後臺、退出的情況下,播放收款到賬提醒。
注意事項:
1、在專案target-Capabilities-Background Modes中要記得勾選Remote notifications 這樣設定才可以正常接收推播。並且在設定推播的時候,一定要帶上這個欄位:"mutable -content" ,只有將該欄位設定為1,才可以正常實現功能。
2.音樂檔案要匯入的時候,在target和NotificationServiceExtension的:Build Phases --> Copy BumdleResources-->add資原始檔
3.模擬推播可以選擇pusher工具模擬測試,附Git地址:pusher,需要選擇推播證書和手機的DeviceToken
因為之前沒有做過此類功能,也是借鑑了很多大牛的解決方案,每個借鑑都有帶的連結,如果有侵權請聯絡我刪除。目前就總結這麼多,有更好的想法希望可以在評論裡一起交流。
參考檔案:
測試程式碼:DEMO