MyException - 我的异常网
当前位置:我的异常网» 操作系统 » 微信iOS收款到账话音提醒开发总结

微信iOS收款到账话音提醒开发总结

www.MyException.Cn  网友分享于:2013-09-28  浏览:0次
微信iOS收款到账语音提醒开发总结

本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/yYCaPMxHGT9LyRyAPewVWQ

作者:littleliang

一、背景

为了解决小商户老板们在频繁交易中不方便核对、确认到账的痛点,产品MM提出了新版本需要支持收款到账语音提醒功能。这篇文章总结了开发过程中遇到的坑和一些小技巧。

二、技术方案

后台唤醒App

收款到账语音提醒需要收款方在收到款后,播放一段TTS合成语音播报金额,微信在前台时可以通过模板消息将需要播报的金额带下来,再请求TTS数据并播放,但是app在挂起或者被kill掉的情况下要如何请求语音数据并播放呢?
iOS提供了两种方式唤醒处于挂起或已经被kill掉的app。分别是Silent Notification和VoIP Push Notification,客户端在被唤醒之后将获得30s的后台运行时间,这段运行时间足以请求合成语音数据并播放。

1.Silent Notification:
Silent Notification在iOS7以上便可以支持,但是每小时能推送的Silent Notification次数有限制。

2.VoIP Push Notification
VoIP Push Notification则是在iOS8以上才支持的新Push类型,相比于Silent Notification,VoIP Push具有高优先级、低延迟的优势,并且没有次数限制。
对比这两种技术方案,VoIP Push Notification明显更适合用于收款到账语音提醒的唤醒方案。

TTS合成语音

TTS语音合成方案分为离线合成方案和在线合成方案,离线合成方案省去网络请求,合成速度更快,节省网络流量,但是合成音的听起来比较机械,语速和停顿的处理较差一些。如果对合成音的效果要求不是特别高,可以考虑采用iOS自带的AVSpeechSynthesis框架,免去语音库的合入,减少安装包大小。

在线合成方案的效果则相对更像人声,富有感情。考虑到产品体验,我们采用了搜索产品部提供的在线语音合成方案,接入方式可以看这篇文章。合成音格式支持wav,mp3,silk,amr,speex,对比后发现,在合成相同文本的情况下,amr的压缩率最高,但是能听到音质下降明显。silk格式压缩率次高,且能保持相对清晰的音质,单条合成语音大小在2KB左右。

唤醒后播放音频文件

在请求到合成语音后,要在后台或者锁屏状态下播放音频文件,AVAudio Session的Category值需要使用AVAudioSessionCategoryPlayback或是AVAudioSessionCategoryPlayAndRecord,CategoryOptions根据实际需要可选择MixWithOthers(与其他声音混音)或是DuckOthers(调低其他声音的音量)。

需要注意的是,只有iOS10以上才支持app被唤醒后在后台/锁屏状态下播放音频。所以iOS10以下的设备,在收到VoIP Push后只能在local push上设定一段固定铃声,这也是为什么iOS10以下只有“微信支付收款到账”,而没有后面具体的金额数值。

三、静音开关检测

不幸的是,在产品发布后没多久就受到了某互联网大佬的吐槽。

从产品体验上来说,收款到账的金额播报是随着local push的弹出一起播放的,更像是一种特殊的push铃声,而苹果对push铃声的处理是受到静音开关控制的,所以讲道理,这个吐槽是合理的。然而前面提到App在被VoIP Push唤醒之后,需要将AudioSessionCategory设置为AVAudioSessionCategoryPlayback或AVAudioSessionCategoryPlayAndRecord才可以在后台播放音频文件,这两种模式是不受静音开关控制的。要实现这个需求,就必须获取当前静音开关的状态。而苹果在iOS5之后并没有明确地提供一种方式让开发获取静音开关的状态,这就陷入了一个尴尬的局面。

苹果在iOS5之前可以使用以下方式监听静音键开关


- (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之后便禁止了使用这种方式监听静音按键,背后的原因应该是苹果希望开发者使用AVAudioSession来提供统一的音频播放效果。

最后我在Reddit上找到了一种曲线救国的方式,实现起来也不复杂:使用AudioServicesPlaySystemSound播放一段0.2s的空白音频,并监听音频播放完成事件,如果从开始播放到回调完成方法的间隔时间小于0.1s,则意味当前静音开关为开启状态。


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);
    }
}

四、设置声音阈值

另外一个用户反馈较多的问题是听不到播报声音,通过查看日志发现是触发语音播报时,用户设置的系统音量过小所导致。首先想到的解决方案是直接设置AVAudioPlayer的volume(或者是AudioQueue中的kAudioQueueParam_Volume),然而实验过后发现这样行不通,volume属性受制于系统音量(比如系统volume是0.5,AVAudioPlayer的音量是0.6,则最终的音量为0.5*0.6 =0.3)。要解决音量过小的问题,还是需要通过调节系统音量。最终的解决方案借鉴了进入收付款展示二维码时自动调节屏幕亮度的方案:如果屏幕亮度未达到阈值,则调高屏幕亮度到阈值,离开页面时,将亮度设回原亮度。同理,播放提示音时,若用户设置的系统音量小于阈值,则调节到阈值。提示音播放完毕后,将提示音调回原音量。

控制系统音量有两种方式:

方式一:通过MPMusicPlayerController设置音量

MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer];
//This property is deprecated -- use MPVolumeView for volume control instead.
mpc.volume = 0;  //0.0~1.0

第一种方式简单粗暴,在设置的时候会弹出系统音量提示框,如果用户在使用app的过程突然弹出音量框,会对用户造成困扰,不建议使用这种方式,并且苹果在iOS7.0以后已将该属性标为deprecated。

方式二:通过MPVolumeView设置音量

第二种方式则是将一个看不见的MPVolumeView添加到当前视图上,系统音量提示框就不会显示了
需要注意的是,在调节完系统音量需要将MPVolumeView移除,否则后续用户手动调节音量会出现系统音量提示框不显示的情况。

调节音量的方式,则是先取到MPVolumeView中名为MPVolumeSlider的子View,并对其发送模拟用户操作的事件。

- (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];
    }
}

文章评论

程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
程序员应该关注的一些事儿
程序员应该关注的一些事儿
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为什么程序员都是夜猫子
为什么程序员都是夜猫子
鲜为人知的编程真相
鲜为人知的编程真相
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
每天工作4小时的程序员
每天工作4小时的程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
漫画:程序员的工作
漫画:程序员的工作
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
我是如何打败拖延症的
我是如何打败拖延症的
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员都该阅读的书
程序员都该阅读的书
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
一个程序员的时间管理
一个程序员的时间管理
中美印日四国程序员比较
中美印日四国程序员比较
旅行,写作,编程
旅行,写作,编程
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
我的丈夫是个程序员
我的丈夫是个程序员
 程序员的样子
程序员的样子
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
如何成为一名黑客
如何成为一名黑客
程序员必看的十大电影
程序员必看的十大电影
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
编程语言是女人
编程语言是女人
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序员的鄙视链
程序员的鄙视链
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
10个调试和排错的小建议
10个调试和排错的小建议
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
代码女神横空出世
代码女神横空出世
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有