苹果微信视频提醒设置(如何让iOS推送播放语音)

首页教程更新时间:2023-02-09 16:09:46
一:背景

iOS 推送播放语音的需求调研,即收到推送后,播放推送的文案,文案的内容不固定。类似于支付宝和微信的收款到账语音。

二:开发过程a. Notification Service Extension

苹果微信视频提醒设置,如何让iOS推送播放语音(1)

项目添加了Notification Service Extension之后的逻辑,和没添加之前有所不同。如下图:添加了之后,接受到推送时,会触发Notification Service Extension中的方法,在这个方法中,可以修改推送的标题、内容、声音。然后把修改后的推送展示出来。

通知栏的生命周期:

要注意的是,Notification Service Extension和主项目不是同一个Target,所以主项目的文件和这个Target文件是不共享的。

创建步骤如下:

苹果微信视频提醒设置,如何让iOS推送播放语音(2)

苹果微信视频提醒设置,如何让iOS推送播放语音(3)

@interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @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]; // 修改推送的声音,自定义铃声支持的声音格式包括,aiff、wav以及wav格式,铃声的长度必须小于30s,否则系统会播放默认的铃声。 // self.bestAttemptContent.sound = [UNNotificationSound soundNamed:@"a.wav"]; // 播放处理 [self playVoiceWithInfo:self.bestAttemptContent.userInfo]; self.contentHandler(self.bestAttemptContent); } - (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); } - (void)playVoiceWithInfo:(NSDictionary *)userInfo { NSLog(@"NotificationExtension content : %@",userInfo); NSString *title = userInfo[@"aps"][@"alert"][@"title"]; NSString *subTitle = userInfo[@"aps"][@"alert"][@"subtitle"]; NSString *subMessage = userInfo[@"aps"][@"alert"][@"body"]; NSString *isRead = userInfo[@"isRead"]; NSString *isUseBaiDu = userInfo[@"isBaiDu"]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDuckOthers error:nil]; [[AVAudioSession sharedInstance] setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; // Ps: 下面代码示例并没有多条播放的处理,还请注意 if ([isRead isEqual:@"1"]) { // 播放语音 if ([isUseBaiDu isEqual:@"1"]) { // 使用百度离线语音播放 [[BaiDuTtsUtils shared] playBaiDuTTSVoiceWithContent:title]; } else { // 使用系统语音播放 [[AppleTtsUtils shared] playAppleTTSVoiceWithContent:title]; } } else { // 无需播放语音 } } @end

其中AppleTtsUtils中实现如下,大致就是使用AVSpeechSynthesizer直接播放,设置音量和语速,需要注意的是,

#import "AppleTTSUtils.h" #import <AVFoundation/AVFoundation.h> #import <AVKit/AVKit.h> @interface AppleTtsUtils ()<AVSpeechSynthesizerDelegate> @property (nonatomic, strong) AVSpeechSynthesizer *speechSynthesizer; @property (nonatomic, strong) AVSpeechSynthesisVoice *speechSynthesisVoice; @end @implementation AppleTtsUtils (instancetype)shared { static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self class] new]; }); return instance; } - (BOOL)isNumber:(NSString *)str { if (str.length == 0) { return NO; } NSString *regex = @"[0-9]*"; NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",regex]; if ([pred evaluateWithObject:str]) { return YES; } return NO; } - (void)playAppleTtsVoiceWithContent:(NSString *)content { if ((content == nil) || (content.length <= 0)) { return; } // 数字转语音,采用zh-CN的voice后,数字的播放方式是几万几千几百几十几这种,故而采用数字后面拼接空格的方式来处理;遍历内容的每一个字符串,如果是数字,则拼接一个空格到后面,最后播放时数字就会一个个读出来。 NSString *newResult = @""; for (int i = 0; i < content.length; i ) { NSString *tempStr = [content substringWithRange:NSMakeRange(i, 1)]; newResult = [newResult stringByAppendingString:tempStr]; if ([self deptNumInputShouldNumber:tempStr] ) { newResult = [newResult stringByAppendingString:@" "]; } } // Todo: 英文转语音 AVSpeechUtterance *utterance = [AVSpeechUtterance speechUtteranceWithString:newResult]; utterance.rate = AVSpeechUtteranceDefaultSpeechRate; utterance.voice = self.speechSynthesisVoice; utterance.volume = 1.0; utterance.rate = AVSpeechUtteranceDefaultSpeechRate; [self.speechSynthesizer speakUtterance:utterance]; } - (AVSpeechSynthesizer *)speechSynthesizer { if (!_speechSynthesizer) { _speechSynthesizer = [[AVSpeechSynthesizer alloc] init]; _speechSynthesizer.delegate = self; } return _speechSynthesizer; } - (AVSpeechSynthesisVoice *)speechSynthesisVoice { if (!_speechSynthesisVoice) { _speechSynthesisVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"]; } return _speechSynthesisVoice; } - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didStartSpeechUtterance:(AVSpeechUtterance *)utterance { NSLog(@"didStartSpeechUtterance"); } - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didCancelSpeechUtterance:(AVSpeechUtterance *)utterance { NSLog(@"didCancelSpeechUtterance"); } - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didPauseSpeechUtterance:(AVSpeechUtterance *)utterance { NSLog(@"didPauseSpeechUtterance"); } - (void)speechSynthesizer:(AVSpeechSynthesizer *)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance *)utterance { NSLog(@"didFinishSpeechUtterance"); [self.speechSynthesizer stopSpeakingAtBoundary:AVSpeechBoundaryWord]; // // 每一条语音播放完成后,我们调用此代码,用来呼出通知栏 // 可通过Block回掉暴露给上层 // self.contentHandler(self.bestAttemptContent); } b. 百度TTS离线SDK添加

  1. 打开百度智能控制台,选中应用列表,创建新的要测试的应用,创建后会有,这里bundleId要写创建的对应的Notification Service Extension的bundleId,而不是主项目的bundleId,一定要注意!!!如下图

  2. 左侧选中离线SDK管理,点击添加,然后选中刚刚创建的应用,点击完成后,点击下载序列号列表,然后把AppId、AppKey、SecretKey、以及序列号存储,用于初始化离线SDK。如下图

  3. 左侧选中离线SDK管理时,点击右边的下载SDK,以及开发文档,按照SDK的说法❝集成指南: 强烈建议用户首先运行SDK包中的Demo工程,Demo工程中详细说明了语音合成的使用方法,并提供了完整的示例。一般情况下,您只需参照demo工程即可完成所有的集成和配置工作。
  4. 所以,把SDK下载好了之后,打开BDSClientSample项目,然后把TTSViewController.mm文件中的APP_ID、API_KEY、SECRET_KEY和SN改为刚刚申请的,然后运行测试,看能否正常播放语音,播放成功说明申请的没有问题,就可以继续往项目中集成,要不然,集成到项目中发现不播放,会怀疑是SDK的问题。,以为集成后调试确实很容易让人怀疑人生。
  5. 把SDK解压后的BDSClientHeaders、BDSClientLib、BDSClientResource文件夹拖拽到Notification Service Extension的target下,注意勾选copy选项,然后把BDSClientLib文件夹下的.gitignore删除,要不然编译会失败,真的,不骗人,,踩坑指南

  6. 添加依赖的系统库,参考BDSClientSample项目中的依赖,注意添加到Notification Service Extension的target下,如下图:

  7. done,编译Notification Service Extension的target,注意选对target,噢噢,这个地方还有个问题,新创建的target是根据Xcode的版本来的,所以还需要修改一下这个target兼容的最低target,要不然默认可能是14.4,然后运行调试不报错,能正常运行,但是断点不走,惊不惊喜,。

  8. 添加百度语音处理代码到Notification Service Extension的target下,如上面写的,BaiDuTtsUtils代码如下
  9. 这里要注意的是, configureOfflineTTS方法中,offlineSpeechData和offlineTextData资源的加载,默认和Demo中写的一致即可,其实是BDSClientResource文件夹下TTS文件夹中的内容,如果下载的有别的语音文件,这里就加载自己下载的语音文件。

#import "BaiDuTtsUtils.h" #import "BDSSpeechSynthesizer.h" // 百度TTS NSString* BaiDuTTSAPP_ID = @"Your_APP_ID"; NSString* BaiDuTTSAPI_KEY = @"Your_APP_KEY"; NSString* BaiDuTTSSECRET_KEY = @"Your_SECRET_KEY"; NSString* BaiDuTTSSN = @"Your_SN"; @interface BaiDuTtsUtils ()<BDSSpeechSynthesizerDelegate> @end @implementation BaiDuTtsUtils (instancetype)shared { static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self class] new]; }); return instance; } #pragma mark - baidu tts -(void)configureOfflineTTS{ NSError *err = nil; NSString* offlineSpeechData = [[NSBundle mainBundle] pathForResource:@"bd_etts_common_speech_m15_mand_eng_high_am-mgc_v3.6.0_20190117" ofType:@"dat"]; NSString* offlineTextData = [[NSBundle mainBundle] pathForResource:@"bd_etts_common_text_txt_all_mand_eng_middle_big_v3.4.2_20210319" ofType:@"dat"]; // #error "set offline engine license" if (offlineSpeechData == nil || offlineTextData == nil) { NSLog(@"离线合成 资源文件为空!"); return; } err = [[BDSSpeechSynthesizer sharedInstance] loadOfflineEngine:offlineTextData speechDataPath:offlineSpeechData licenseFilePath:nil withAppCode:BaiDuTTSAPP_ID withSn:BaiDuTTSSN]; if(err){ NSLog(@"Offline TTS init failed"); return; } } - (void)playBaiDuTTSVoiceWithContent:(NSString *)voiceText { NSLog(@"TTS version info: %@", [BDSSpeechSynthesizer version]); [BDSSpeechSynthesizer setLogLevel:BDS_PUBLIC_LOG_VERBOSE]; // 设置委托对象 [[BDSSpeechSynthesizer sharedInstance] setSynthesizerDelegate:self]; [self configureOfflineTTS]; [[BDSSpeechSynthesizer sharedInstance] setPlayerVolume:10]; [[BDSSpeechSynthesizer sharedInstance] setSynthParam:[NSNumber numberWithInteger:5] forKey:BDS_SYNTHESIZER_PARAM_SPEED]; // 开始合成并播放 NSError* speakError = nil; NSInteger sentenceID = [[BDSSpeechSynthesizer sharedInstance] speakSentence:voiceText withError:&speakError]; if (speakError) { NSLog(@"错误: %ld, %@", (long)speakError.code, speakError.localizedDescription); } } - (void)synthesizerStartWorkingSentence:(NSInteger)SynthesizeSentence { NSLog(@"Began synthesizing sentence %ld", (long)SynthesizeSentence); } - (void)synthesizerFinishWorkingSentence:(NSInteger)SynthesizeSentence { NSLog(@"Finished synthesizing sentence %ld", (long)SynthesizeSentence); } - (void)synthesizerSpeechStartSentence:(NSInteger)SpeakSentence { NSLog(@"Began playing sentence %ld", (long)SpeakSentence); } - (void)synthesizerSpeechEndSentence:(NSInteger)SpeakSentence { NSLog(@"Finished playing sentence %ld", (long)SpeakSentence); } @end c. 调试

刺激的部分来了,上面都编译通过了没问题,使用推送调试,先运行一次主项目,然后选中Notification Service Extension Target运行,didReceiveNotificationRequest:withContentHandler:方法中添加断点,,给自己推送消息,会发现断点走到了这里,说明target的创建没有问题。

然后控制推送参数的,isRead和isBaiDu参数,决定推送过来的语音是否走百度的语音播放。噢,说到推送参数,这个地方还需要在payload推送参数中添加"mutable-content = 1"字段,eg:

{ "aps": { "alert": { "title":"标题", "subtitle: "副标题", "body": "内容" }, "badge": 1, "sound": "default", "mutable-content": "1", } }

推送调试,会发现运行正常,但是语音没有播放,不管是系统的还是百度的,哈哈哈,崩溃不。仔细看控制台,会发现,报错如下

Ps: iOS 12.0之后,在Notification Service Extension调用系统播放AVSpeechSynthesizer时报的错误。

[AXTTSCommon] Failure starting audio queue alp! [AXTTSCommon] _BeginSpeaking: couldn't begin playback

Ps: iOS 12.0之后,在Notification Service Extension调用百度的SDK直接播放时报的错误。

[ERROR][AudioBufPlayer.mm:1088]AudioQueue start errored error: 561015905 (!pla) [ERROR][AudioBufPlayer.mm:1099]Can't begin playback while in background!

都是一个意思,即不能在后台播放音频。怎么解决呢,当然是添加backgroundMode字段了,打开主工程的Signing&Capabilities,添加backgrondModes,勾选Audio, Airplay, and Picture in Picture,如下图

苹果微信视频提醒设置,如何让iOS推送播放语音(4)

苹果微信视频提醒设置,如何让iOS推送播放语音(5)

OK,try again! 再次推送,会发现————还是不行,同样的报错,哈哈哈,绝望不,不好意思,我收敛一下,这个地方其实添加的没错,只不过要注意

❝在Notification Service Extension配置了之后,发现收到通知后还是不会播放声音,在这个Extension的Target下打开plist,添加Required background modes字段,里面item0写上App plays audio or streams audio/video using AirPlay后,再次调试,发现百度的语音即可播放。这种方式审核时不被通过,因为这个Extension的target其实是没有backgroundMode的设置的,从Signing&Capabilities中可以看出,直接添加backgroundMode是没有的。故而如果不是上线到苹果商店的,只是公司内部分发,可以用这种方式。

添加了之后,再次推送,就会发现百度的语音就可以播放了,而且数字和英文、中文播放都十分完美,除了价格有些感人,其他的没毛病。而系统的播放语音,如果先推送系统的,会发现不能播放,还是同样的报错;但是如果先推送了走百度的,百度播放了之后,再推送系统的,就会发现系统的也能播报,但是系统播报的英文和数字会有问题,记得处理,可以听一下英文字母E的发音,发音额。。。解决方案——暂无,还没找到,建议走第三方合成的语音。

由于项目不需要上线商店,所以到这里其实就结束了。但是对于上线到商店到应用来说,这种处理方法是不行的,上线到商店的应用其实只有播放固定格式的音频一种解决方法,即替换推送的声音。使用固定格式的音频、或者固定格式的合成音频替换掉推送的声音,或者采用远程推送静音,发送多个本地通知,各个本地通知的声音替换掉这种方法。这些是从末尾的参考中得到的启示。

三、结论

直接上图,整理后的思维导图如下,大部分比较复杂的处理逻辑其实是iOS 12.0之后的处理。

苹果微信视频提醒设置,如何让iOS推送播放语音(6)

引用

作者:王徳亮

来源:微信公众号:搜狐技术产品

出处:https://mp.weixin.qq.com/s/Gpu4sw0Zn4IBS-k5wvF__g

,
图文教程
相关文章
热门专题
推荐软件
奇热小说
奇热小说
下载
QQ2019手机版
QQ2019手机版
下载
王者荣耀
王者荣耀
下载
百度浏览器迷你版
百度浏览器迷你版
下载
2345浏览器手机版
2345浏览器手机版
下载
网易邮箱
网易邮箱
下载
爱奇艺
爱奇艺
下载
网易云音乐
网易云音乐
下载
WPSOffice
WPSOffice
下载
优酷
优酷
下载
谷歌浏览器(Chrome)
谷歌浏览器(Chrome)
下载
迅雷看看播放器
迅雷看看播放器
下载
UC浏览器
UC浏览器
下载
QQ音乐
QQ音乐
下载
阿里旺旺买家版v9.12.10C官方版
阿里旺旺买家版v9.12.10C官方版
下载
360安全卫士v12.1官方版
360安全卫士v12.1官方版
下载
猜你喜欢
甜甜圈小郡手机版
甜甜圈小郡手机版
下载
大粮淘粮
大粮淘粮
下载
战斗酷跑修改版
战斗酷跑修改版
下载
猎人刺客
猎人刺客
下载
街头霸王5街机版原画全解锁完美存档
街头霸王5街机版原画全解锁完美存档
下载
猜猜秀
猜猜秀
下载
巴中生活
巴中生活
下载
十分健身
十分健身
下载
甜水果糖果
甜水果糖果
下载
华为ec121驱动Forxp/vista
华为ec121驱动Forxp/vista
下载
干仗我最牛
干仗我最牛
下载
油漆的战争2021
油漆的战争2021
下载
我的世界惊人的龙mod
我的世界惊人的龙mod
下载
电声功率转换计算v2013绿色版
电声功率转换计算v2013绿色版
下载
CoreFTPLitev2.2.1929免费版
CoreFTPLitev2.2.1929免费版
下载
开心超人大连萌
开心超人大连萌
下载