关于APP上语音播报的完整实现(iOS篇)

本文介绍了一种在iOS应用中实现定制化到账语音通知的方法,包括如何通过推送和语音文件结合来实现不同金额的播报,并解决了后台播放及音量调节等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前一段时间,一个“支付宝到账100万”的铃声在网络上火了起来,其实这在APP上,特别支付类的应用里,经常用到,今天我们谈一下其实现方法,给类似这种场景的开发人员一个参考吧。

首先,我们这次是基于推送+语音的方式来实现。

使用sound字段

我们都知道,我们可以在进行推送的时候,指定sound的文件名,来播放指定声音文件。

于是,录好一个声音文件,暂且叫“tts_default.mp3”吧,加入到主工程中。

服务端收到一笔款项的时候,往消息中心发起一个推送,推送的格式和内容如下:

{"aps":{"alert":"XXX到账一笔","badge":1,"sound":"tts_default.mp3"}}

这样,APP在接受到通知的时候,弹出一个通知框,显示“XXX到账一笔”,并伴随一个声音,播放的是语音文件tts_default.mp3。

播报金额

如果收到一笔钱,如果能播放具体金额就更好了,因为金额是变化的,你不可能在工程里添加许多“tts_default.mp3”文件,那我们只有合成金额,在AVFoundation里,有合成声音的API,在第三方,也有如百度、讯飞一样的第三方合成声音的接口,我们测试一下,还是比较生硬。这里我们仿地铁、车站的广播,录了一些基础的声音、和一些数字,我们自己来合成所需的声音。

假如,你要实现的语音格式是这样的:钱到啦到账xx.xx元。

我们录制并预置了一些语音文件打在包里,这些文件包括:

tts_pre.mp3   对应文字为:钱到啦到账

tts.yuan.mp3 对应文字为:元。

另外还有一些表表示数字的,如0、1、2、3、4、5、6、7、8、9、十、百、千、万、点

对应的声音文件为:

tts_0.mp3 ~ tts_9.mp3、tts_ten.mp3、tts_hundred.mp3、tts_thousand.mp3、tts_ten_thousand.mp3、tts_dot.mp3

当我们想播放声音“钱到啦到账0.25元”的时候,我们就可以依次播放声音文件:

tts_pre.mp3、tts_0.mp3、tts_dot.mp3、tts_2.mp3、tts_5.mp3、tts.yuan.mp3

就可以了。

这里牵扯到一个金额转语音文件的算法,后面的Demo有实现,可以参考一下:

-(NSString *)wordsStringFromAmount:(NSString *)numstr;

流程是这样的:

1、后端收到钱,给商家发起一个推送,格式为:

{"aps":{"alert":"钱到啦到账0.25元","badge":1,"amount":0.25, "sound":"tts_default.mp3"}} 

2、客户端收到推送,处理金额字段amount,转成对应的播放文件数组。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
    [[BPAudioManager sharedPlayer] playPushInfo:userInfo completed:nil] ;
}

3、开始播放声音文件。

后台播放

当APP在前台的时候,上面那种处理方法是没有问题的,在后台的时候,只会播放一个“tts_default.mp3”这个通用型的语音文件,也没有问题的,但是在后台和APP退出的情况下,playPushInfo这个方法执行一些处理,并播放语音是不可行的,所以还借助其他的方法,好在苹果在iOS10的时候,发布了UNNotificationServiceExtension扩展,关于此扩展,可以网上选择一些资料,主要的核心思想就是,在远程推送到底设备之前,给你一个修改的机会,我们知道,推送体是有限制的,而且推送体大小也会影响推送的效率,借助这个,我们可以修改标题、内容,也可以从网络上请求到内容,再去合成一个新的推送。我们这里不修改内容,主要是用来播放语音。

要使用这个扩展,和其他扩展一样,新建一个target,找到这个模版,然后下一步,就好了。

系统会自动实现两个方法:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler ;
- (void)serviceExtensionTimeWillExpire;

前者,你需要在这里做一些操作,修改内容,当你完成后,通知系统,这时候,推送才会显示出来。我们这里主要处理推送,并播放声音;后者会在超时的情况下调用。如:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    //step1: 标记该推送已经在这里处理过了
    NSMutableDictionary *dict = [self.bestAttemptContent.userInfo mutableCopy] ;
    [dict setObject:[NSNumber numberWithBool:YES] forKey:@"hasHandled"] ;
    self.bestAttemptContent.userInfo = dict ;
    
    //step2: 忽略推送中的默认语音文件(有可能是那个recieved.mp3)
    self.bestAttemptContent.sound = [UNNotificationSound defaultSound] ;
    
    //step3: 处理推送信息,播放语音
    [[BPAudioManager sharedPlayer] playPushInfo:self.bestAttemptContent.userInfo completed:^{
        // 播放完成后,通知系统
        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);
}

要激活UNNotificationServiceExtension扩展,需要在字段中添加mutable-content字段,所以新的推送体为:

{"aps":{"alert":"钱到啦到账0.25元","badge":1,"mutable-content":1,"amount":0.25, "sound":"tts_default.mp3"}} mutable-content":1,"amount":0.25, "sound":"tts_default.mp3"}} 

BPAudioManager

我们定义了一个声音处理的中间类,因为扩展和APP本身都会使用这个类,所以新建这个文件的时候,注意勾选Targets

- (void) playPushInfo:(NSDictionary *)userInfo completed:(BPAudioPlayCompleted)completed {
    
    //获取aps
    NSDictionary *aps =  [userInfo objectForKey:@"aps"] ;
    
    //判断是否需要播报语音,因为所有的推送,都会走到这里
    BOOL playaudio =  [[aps objectForKey:@"playaudio"] boolValue] ;
    if(!playaudio) {
        if(completed != nil) {
            completed() ;
        }
    }
    // 处理
    else {
        self.completed = completed ;
        NSString *amount = [aps objectForKey:@"amount"] ;
        NSArray* arrAudioFiles = [self getAudioFilesWithAmount:amount] ;
        [self playAudioFiles:arrAudioFiles] ;
    }
}

先处理金额,得到语音文件的数组,播放语音这里直接用循环播放的方式了

// 播放声音文件
- (void) playAudioFiles {
    // 1.获取要播放音频文件的URL
    NSString *fileName = [audioFiles objectAtIndex:audioIndex] ;
    NSString *path = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] resourcePath], fileName] ;
    NSURL *fileURL = [NSURL fileURLWithPath:path];
    
    // 2.创建 AVAudioPlayer 对象
    self.audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:fileURL error:nil];
    // 4.设置循环播放
    self.audioPlayer.numberOfLoops = 0 ;
    self.audioPlayer.delegate = self;
    // 5.开始播放
    [self.audioPlayer prepareToPlay] ;
    [self.audioPlayer play];
}
// 播放完成回调
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
    audioIndex += 1 ;
    if(audioIndex < audioFiles.count) {
        [self performSelectorOnMainThread:@selector(playAudioFiles) withObject:nil waitUntilDone:NO] ;
    }
    else {
        [self setNormalVolume] ;
        [self disactivePlayback] ;
        [self performSelectorOnMainThread:@selector(playCompleted) withObject:nil waitUntilDone:NO] ;
    }
}

到这里,基本就完成了,在后台、退出后台的情况下,可以正常语音播报了。

音量调节

有时候,我们不小心把声音关闭了,或者音量很小,或者静音模式下,那这个时候,播放的声音就可能听不见了,为了防止这个情况发生,我们在播放的时候,适当处理一下,是非要有必要的。

// 设置高音量
- (void) setHighVolume {
    MPVolumeView*volumeView = [[MPVolumeViewalloc] init];
    UISlider*volumeViewSlider = nil;
    for(UIView*view in[volumeView subviews]){
        if([view.class.descriptionisEqualToString:@"MPVolumeSlider"]){
            volumeViewSlider = (UISlider*)view;
            break;
        }
    }
    
    // 获取系统原来的音量,用于还原
    userVolume= volumeViewSlider.value;
    
    // 留点余地,设置0.9吧, 值在0.0~1.0之间
    if(userVolume< 0.9f) {
        // 改变系统音量
        [volumeViewSlider setValue:0.9fanimated:NO];
        // 发一个事件使之生效
        [volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}

然后播放完成的时候,会设置会正常音量

// 设置回正常音量
- (void) setNormalVolume {
    MPVolumeView*volumeView = [[MPVolumeViewalloc] init];
    UISlider* volumeViewSlider = nil;
    for(UIView*view in[volumeView subviews]){
        if([view.class.descriptionisEqualToString:@"MPVolumeSlider"]){
            volumeViewSlider = (UISlider*)view;
            break;
        }
    }
    if(volumeViewSlider.value!=userVolume) {
        [volumeViewSlider setValue:userVolumeanimated:NO];
        [volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
}

然后静音处理:

// 静音模式下,依然可以播放
- (void) activePlayback {
    [[AVAudioSessionsharedInstance] setCategory:AVAudioSessionCategoryPlaybackerror:NULL];
    [[AVAudioSessionsharedInstance] setActive:YESerror:NULL];
}
//回归正常
- (void)disactivePlayback {
    [[AVAudioSessionsharedInstance] setActive:NOerror:NULL];
}

至此,语音播报算是完成了。

1、在iOS10以下,推送利用sound字段,前台可以正常播放,后台、退出的情况下,播放通用声音。

2、iOS以上,推送增加mutable-content字段,可以完美播放。

3、我们增加了一些机制,在低音和静音模式下,也可以正常工作。

附演示Demo

https://github.com/WinterXIE/PushAudio

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值