引言
需求:收款到账语音提醒功能
NSE是比Voip更优雅的解决方案,完成迁移后,总体代码量也比Voip方案少了不少。
https://kunnan.blog.csdn.net/article/details/103702284
遇到的问题:短时间内收到多条播报通知时,后面的通知会顶掉前面的通知,导致前面的通知播报不完整。
解决方式:增加一个消息队列,将所有需要播报的通知都添加到队列中,当前面的消息播放完毕后,再播放后面的消息。
音频的播放时间可以让后台通过payload推送,如果是自己离线合成的音频可以通过自己计算:
播放时间 =(音频大小 - 音频头)/ (采样频率 * 采样精度 * 通道数)
。
其他相关问题:由于服务端原因,客户端会收到两条消息一样
解决方案:消息去重
- 双通道触发的apns消息在requestheader上带上同样的apns-collapse-id,后面的通知就会覆盖前面的通知。
- 通过记录已播放的消息单号,后面再重现重复的单号就讲sound设置为一段空白的音频。
I 消息播放队列的实现
1.1 方式一: 使用递归管理消息推送
AudioTool.sharedPlayer
单利对象心中一个数组属性,用来存储待处理的消息
/**
待处理的消息
*/
@property (nonatomic, strong) NSMutableArray *userInfos;
/**
是否正在处理消息
*/
@property (nonatomic, assign) BOOL isProcessing;
- (NSMutableArray *)userInfos{
if(_userInfos == nil){
_userInfos = [NSMutableArray array];
}
return _userInfos;
}
- NotificationService 收到消息时,往待处理的消息集合中添加,并调用递归方法处理消息
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
UNMutableNotificationContent *bestAttemptContent = [request.content mutableCopy];
NSLog(@"NotificationService_%@: dict->%@", NSStringFromClass([self class]), bestAttemptContent.userInfo);
bestAttemptContent.sound = nil;
[AudioTool.sharedPlayer.userInfos addObject: bestAttemptContent];
// 调用递归方法处理消息
[self processNotificationContent];
}
- 递归方法的实现:判断退出条件及是否正在处理
- (void)processNotificationContent{
if(AudioTool.sharedPlayer.userInfos.count<1){// 递归的退出条件
return ;
}
if(AudioTool.sharedPlayer.isProcessing){//是否正在处理消息播报
return ;
}
self.bestAttemptContent = AudioTool.sharedPlayer.userInfos.firstObject;
AudioTool.sharedPlayer.isProcessing = YES;
// 调用语音播报方法
[[AudioTool sharedPlayer] playPushInfo:weakSelf.bestAttemptContent.userInfo backModes:YES completed:^(BOOL success) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
NSMutableDictionary *dict = [strongSelf.bestAttemptContent.userInfo mutableCopy] ;
[dict setObject:[NSNumber numberWithBool:YES] forKey:@"hasHandled"] ;
strongSelf.bestAttemptContent.userInfo = dict;
NSLog(@"当前方法是: %@",NSStringFromSelector(_cmd));
strongSelf.contentHandler(strongSelf.bestAttemptContent);
[AudioTool.sharedPlayer.userInfos removeObject:strongSelf.bestAttemptContent];
YJAudioTool.sharedPlayer.isProcessing = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf processNotificationContent];// 循环调用自己
});
}
}];
}
1.2 方式二:NSOperationQueue
一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation
添加到一个NSOperationQueue
(操作队列)中去执行,而且是异步执行的。
Operations的执行顺序:先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请>附上原文出处链接及本声明。
原文链接: https://blog.csdn.net/z929118967/article/details/74298904
- (void)addOperation:(NSString *)title {
[[NSOperationQueue mainQueue] addOperation:[self customOperation:title]];
}
- (NSOperation *)customOperation:(NSString *)content {
__weak __typeof__(self) weakSelf = self;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[weakSelf pushNotificationWith:content];
}];
return operation;
}
1.3 备用方案
当扩展处理消息推送的时间超过苹果规定的时候,在serviceExtensionTimeWillExpire
处理。
- (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);//苹果规定,当一条通知达到后,如果在30秒内,还没有呼出通知栏,我就系统强制调用self.contentHandler(self.bestAttemptContent) 来呼出通知栏
}
II 语音播报
2.1 iOS前台可使用系统API进行语音播报
//语音合成
+ (void)Voicebroadcast:(NSString *)str
{
AVSpeechSynthesizer * speechSynthesizer = [[AVSpeechSynthesizer alloc] init];
AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:[NSString stringWithFormat:@"%@",str]];
AVSpeechSynthesisVoice *voiceType = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
utterance.voice = voiceType;
//设置语速
utterance.rate *= 0.9;
//设置音量
utterance.volume = 1;
[speechSynthesizer speakUtterance:utterance];
}
2.2 本地拼接音频+Service Extension
- 付费方案:iOS App处于后台/被杀死的状态仍可进行语言播报的实现方案( 离线合成+Service Extension):https://kunnan.blog.csdn.net/article/details/103702284
- 免费方案:(本地拼接音频+Service Extension)https://download.csdn.net/download/u011018979/84197453
2.3 uni 原生插件(支持iOS Extension)离线推送语音播报
https://blog.csdn.net/z929118967/article/details/127536579?spm=1001.2014.3001.5501
实现原理:
- 获取GroupIdentifier,用于保存本地拼接音频。
从info.plist 获取配置信息GroupIdentifier,这样扩展插件可灵活适用于不同app。
+(NSString*)getAppGroupID{
NSDictionary *infoDic=[[NSBundle mainBundle] infoDictionary];
NSString* kAppGroupID = [infoDic valueForKey:@"KNGroupIdentifier"];
return kAppGroupID;
}
————————————————
版权声明:本文为CSDN博主「iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z929118967/article/details/127536579
- 将音频文件写到AppGroups的Library/Sounds文件夹下,最后更改UNNotificationSound属性即可使通知播报一段自定义的收款到账语音。
https://kunnan.blog.csdn.net/article/details/103702284
see also
更多服务和咨询请关注#公号:iOS逆向
。