iOS開發(fā)微信收款到賬語音提醒功能思路詳解
一、背景
為了解決小商戶老板們在頻繁交易中不方便核對、確認(rèn)到賬的痛點(diǎn),產(chǎn)品MM提出了新版本需要支持收款到賬語音提醒功能。這篇文章總結(jié)了開發(fā)過程中遇到的坑和一些小技巧。
二、技術(shù)方案
后臺喚醒App
收款到賬語音提醒需要收款方在收到款后,播放一段TTS合成語音播報金額,微信在前臺時可以通過模板消息將需要播報的金額帶下來,再請求TTS數(shù)據(jù)并播放,但是app在掛起或者被kill掉的情況下要如何請求語音數(shù)據(jù)并播放呢?
iOS提供了兩種方式喚醒處于掛起或已經(jīng)被kill掉的app。分別是Silent Notification和VoIP Push Notification,客戶端在被喚醒之后將獲得30s的后臺運(yùn)行時間,這段運(yùn)行時間足以請求合成語音數(shù)據(jù)并播放。
1.Silent Notification:
Silent Notification在iOS7以上便可以支持,但是每小時能推送的Silent Notification次數(shù)有限制。
2.VoIP Push Notification
VoIP Push Notification則是在iOS8以上才支持的新Push類型,相比于Silent Notification,VoIP Push具有高優(yōu)先級、低延遲的優(yōu)勢,并且沒有次數(shù)限制。
對比這兩種技術(shù)方案,VoIP Push Notification明顯更適合用于收款到賬語音提醒的喚醒方案。
TTS合成語音
TTS語音合成方案分為離線合成方案和在線合成方案,離線合成方案省去網(wǎng)絡(luò)請求,合成速度更快,節(jié)省網(wǎng)絡(luò)流量,但是合成音的聽起來比較機(jī)械,語速和停頓的處理較差一些。如果對合成音的效果要求不是特別高,可以考慮采用iOS自帶的AVSpeechSynthesis框架,免去語音庫的合入,減少安裝包大小。
在線合成方案的效果則相對更像人聲,富有感情??紤]到產(chǎn)品體驗(yàn),我們采用了搜索產(chǎn)品部提供的在線語音合成方案,接入方式可以看這篇文章。合成音格式支持wav,mp3,silk,amr,speex,對比后發(fā)現(xiàn),在合成相同文本的情況下,amr的壓縮率最高,但是能聽到音質(zhì)下降明顯。silk格式壓縮率次高,且能保持相對清晰的音質(zhì),單條合成語音大小在2KB左右。
喚醒后播放音頻文件
在請求到合成語音后,要在后臺或者鎖屏狀態(tài)下播放音頻文件,AVAudio Session的Category值需要使用AVAudioSessionCategoryPlayback或是AVAudioSessionCategoryPlayAndRecord,CategoryOptions根據(jù)實(shí)際需要可選擇MixWithOthers(與其他聲音混音)或是DuckOthers(調(diào)低其他聲音的音量)。

需要注意的是,只有iOS10以上才支持app被喚醒后在后臺/鎖屏狀態(tài)下播放音頻。所以iOS10以下的設(shè)備,在收到VoIP Push后只能在local push上設(shè)定一段固定鈴聲,這也是為什么iOS10以下只有“微信支付收款到賬”,而沒有后面具體的金額數(shù)值。
三、靜音開關(guān)檢測
不幸的是,在產(chǎn)品發(fā)布后沒多久就受到了某互聯(lián)網(wǎng)大佬的吐槽。

從產(chǎn)品體驗(yàn)上來說,收款到賬的金額播報是隨著local push的彈出一起播放的,更像是一種特殊的push鈴聲,而蘋果對push鈴聲的處理是受到靜音開關(guān)控制的,所以講道理,這個吐槽是合理的。然而前面提到App在被VoIP Push喚醒之后,需要將AudioSessionCategory設(shè)置為AVAudioSessionCategoryPlayback或AVAudioSessionCategoryPlayAndRecord才可以在后臺播放音頻文件,這兩種模式是不受靜音開關(guān)控制的。要實(shí)現(xiàn)這個需求,就必須獲取當(dāng)前靜音開關(guān)的狀態(tài)。而蘋果在iOS5之后并沒有明確地提供一種方式讓開發(fā)獲取靜音開關(guān)的狀態(tài),這就陷入了一個尷尬的局面。
蘋果在iOS5之前可以使用以下方式監(jiān)聽靜音鍵開關(guān)
- (BOOL)isMuted
{
CFStringRef route;
UInt32 routeSize = sizeof(CFStringRef);
OSStatus status = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &routeSize, &route);
if (status == kAudioSessionNoError)
{
if (route == NULL || !CFStringGetLength(route))
return YES;
}
return NO;
}
蘋果在iOS5之后便禁止了使用這種方式監(jiān)聽靜音按鍵,背后的原因應(yīng)該是蘋果希望開發(fā)者使用AVAudioSession來提供統(tǒng)一的音頻播放效果。
最后我在Reddit上找到了一種曲線救國的方式,實(shí)現(xiàn)起來也不復(fù)雜:使用AudioServicesPlaySystemSound播放一段0.2s的空白音頻,并監(jiān)聽音頻播放完成事件,如果從開始播放到回調(diào)完成方法的間隔時間小于0.1s,則意味當(dāng)前靜音開關(guān)為開啟狀態(tài)。
void SoundMuteNotificationCompletionProc(SystemSoundID ssID,void* clientData){
MMSoundSwitchDetector* detecotr = (__bridge MMSoundSwitchDetector*)clientData;
[detecotr complete];
}
- (instancetype)init {
self = [super init];
if (self) {
NSURL *pathURL = [[NSBundle mainBundle] URLForResource:@"mute" withExtension:@"caf"];
if (AudioServicesCreateSystemSoundID((__bridge CFURLRef)pathURL, &_soundId) == kAudioServicesNoError){
AudioServicesAddSystemSoundCompletion(self.soundId, CFRunLoopGetMain(), kCFRunLoopDefaultMode, SoundMuteNotificationCompletionProc,(__bridge void *)(self));
UInt32 yes = 1;
AudioServicesSetProperty(kAudioServicesPropertyIsUISound, sizeof(_soundId),&_soundId,sizeof(yes), &yes);
} else {
MMErrorWithModule(LOGMODULE, @"Create Sound Error.");
_soundId = 0;
}
}
return self;
}
- (void)checkSoundSwitchStatus:(CheckSwitchStatusCompleteBlk)completHandler {
if (self.soundId == 0) {
completHandler(YES);
return;
}
self.completeHandler = completHandler;
self.beginTime = CACurrentMediaTime();
AudioServicesPlaySystemSound(self.soundId);
}
- (void)complete {
CFTimeInterval elapsed = CACurrentMediaTime() - self.beginTime;
BOOL isSwitchOn = elapsed > 0.1;
if (self.completeHandler) {
self.completeHandler(isSwitchOn);
}
}
四、設(shè)置聲音閾值
另外一個用戶反饋較多的問題是聽不到播報聲音,通過查看日志發(fā)現(xiàn)是觸發(fā)語音播報時,用戶設(shè)置的系統(tǒng)音量過小所導(dǎo)致。首先想到的解決方案是直接設(shè)置AVAudioPlayer的volume(或者是AudioQueue中的kAudioQueueParam_Volume),然而實(shí)驗(yàn)過后發(fā)現(xiàn)這樣行不通,volume屬性受制于系統(tǒng)音量(比如系統(tǒng)volume是0.5,AVAudioPlayer的音量是0.6,則最終的音量為0.5*0.6 =0.3)。要解決音量過小的問題,還是需要通過調(diào)節(jié)系統(tǒng)音量。最終的解決方案借鑒了進(jìn)入收付款展示二維碼時自動調(diào)節(jié)屏幕亮度的方案:如果屏幕亮度未達(dá)到閾值,則調(diào)高屏幕亮度到閾值,離開頁面時,將亮度設(shè)回原亮度。同理,播放提示音時,若用戶設(shè)置的系統(tǒng)音量小于閾值,則調(diào)節(jié)到閾值。提示音播放完畢后,將提示音調(diào)回原音量。
控制系統(tǒng)音量有兩種方式:
方式一:通過MPMusicPlayerController設(shè)置音量
MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer]; //This property is deprecated -- use MPVolumeView for volume control instead. mpc.volume = 0; //0.0~1.0
第一種方式簡單粗暴,在設(shè)置的時候會彈出系統(tǒng)音量提示框,如果用戶在使用app的過程突然彈出音量框,會對用戶造成困擾,不建議使用這種方式,并且蘋果在iOS7.0以后已將該屬性標(biāo)為deprecated。
方式二:通過MPVolumeView設(shè)置音量
第二種方式則是將一個看不見的MPVolumeView添加到當(dāng)前視圖上,系統(tǒng)音量提示框就不會顯示了
需要注意的是,在調(diào)節(jié)完系統(tǒng)音量需要將MPVolumeView移除,否則后續(xù)用戶手動調(diào)節(jié)音量會出現(xiàn)系統(tǒng)音量提示框不顯示的情況。
調(diào)節(jié)音量的方式,則是先取到MPVolumeView中名為MPVolumeSlider的子View,并對其發(fā)送模擬用戶操作的事件。
- (void)setSystemVolume:(float)volume {
UISlider* volumeViewSlider = nil;
for (UIView *view in [self.m_privateVoulmeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
volumeViewSlider = (UISlider*)view;
break;
}
}
if (volumeViewSlider != nil) {
[volumeViewSlider setValue:volume animated:NO];
//通過send
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
總結(jié)
以上所述是小編給大家介紹的iOS開發(fā)微信收款到賬語音提醒功能思路詳解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
詳解ios中的SQL數(shù)據(jù)庫文件加密 (使用sqlcipher)
本篇文章主要介紹了ios中的SQL數(shù)據(jù)庫文件加密 (使用sqlcipher),具有一定的參考價值,這里整理了詳細(xì)的代碼,感興趣的小伙伴們可以參考一下。2016-12-12
iOS狀態(tài)欄frame計算問題的實(shí)現(xiàn)
這篇文章主要介紹了iOS狀態(tài)欄frame計算問題的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
使用UITextField限制只可輸入中,英文,數(shù)字的方法
在我們?nèi)粘i_發(fā)中經(jīng)常遇到一些情況,要UITextField只能輸入某一種特定的字符.比如大寫A-Z或者小寫a-z,或者漢字.或者數(shù)字.那么該如何實(shí)現(xiàn)呢,下面通過這篇文章來看看吧。2016-09-09
iOS利用NSMutableAttributedString實(shí)現(xiàn)富文本的方法小結(jié)
這篇文章主要給大家介紹了關(guān)于iOS利用NSMutableAttributedString如何實(shí)現(xiàn)富文本的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
詳解2016 cocoapods的安裝和使用以及版本升級遇到的問題
CocoaPods是一個負(fù)責(zé)管理iOS項(xiàng)目中第三方開源庫的工具。這篇文章主要介紹了2016 cocoapods的安裝和使用以及版本升級遇到的問題,有需要的可以了解一下。2016-12-12
iOS開發(fā)中文件的上傳和下載功能的基本實(shí)現(xiàn)
這篇文章主要介紹了iOS開發(fā)中文件的上傳和下載功能的基本實(shí)現(xiàn),并且下載方面講到了大文件的多線程斷點(diǎn)下載,需要的朋友可以參考下2015-11-11
iphone的safari瀏覽器中實(shí)現(xiàn)全屏瀏覽的方法
這篇文章主要介紹了iphone的safari瀏覽器中實(shí)現(xiàn)全屏瀏覽的方法,同時介紹了Add to Home Screen功能的實(shí)現(xiàn)方法,需要的朋友可以參考下2014-06-06
全面解析iOS應(yīng)用中自定義UITableViewCell的方法
這篇文章主要介紹了iOS應(yīng)用開發(fā)中自定義UITableViewCell的方法,示例為傳統(tǒng)的Obejective-C語言,需要的朋友可以參考下2016-04-04

