How to Implement iOS Voice Payment Alerts: Push Wake‑up, TTS, and Mute Detection
This article explains how to enable voice reminders for payment receipt on iOS by using VoIP push notifications to wake a suspended app, integrating online/offline TTS synthesis, handling audio playback in background, detecting the mute switch, and adjusting system volume thresholds.
1. Background
To solve small merchants' difficulty of confirming receipt during frequent transactions, product MM proposed a new version that supports voice reminders for payment receipt. This article summarizes the pitfalls and tips encountered during development.
2. Technical Solution
Background App Wake‑up
Voice reminders require the payee’s app to play a TTS‑synthesized amount after receiving payment. When the app is in the foreground, a template message can deliver the amount, request TTS data and play it. When the app is suspended or killed, iOS offers Silent Notification and VoIP Push Notification to wake the app, granting about 30 seconds of background execution to fetch and play the audio.
1. Silent Notification – supported on iOS 7+, limited push frequency.
2. VoIP Push Notification – supported on iOS 8+, high priority, low latency, no frequency limit.
Comparing the two, VoIP Push is more suitable for voice‑reminder wake‑up.
TTS Synthesis
Two approaches: offline synthesis (no network, faster, but mechanical sound) and online synthesis (more natural). For modest quality, iOS’s AVSpeechSynthesis can be used. For richer experience, the team adopted an online service provided by the search product team. Supported formats include wav, mp3, silk, amr, speex. Among them, amr offers highest compression but noticeable quality loss; silk provides a good trade‑off with ~2 KB per utterance.
Playing Audio After Wake‑up
To play audio while the app is in background or locked, AVAudioSession category must be set to AVAudioSessionCategoryPlayback or AVAudioSessionCategoryPlayAndRecord, optionally with MixWithOthers or DuckOthers.
Only iOS 10+ supports audio playback in background after being woken; earlier versions can only play a fixed ringtone via local push.
3. Silent‑Switch Detection
After release, users complained that the amount was not spoken on devices with the mute switch on. Push‑tone playback respects the mute switch, but audio played after a VoIP‑Push with AVAudioSessionCategoryPlayback bypasses it, requiring explicit mute‑switch status detection. iOS 5 and earlier allowed route‑based detection, but this was deprecated.
- (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;
}A workaround uses AudioServicesPlaySystemSound to play a 0.2 s silent clip and measures the callback interval; if it is less than 0.1 s, the mute switch is on.
void SoundMuteNotificationCompletionProc(SystemSoundID ssID, void* clientData) {
MMSoundSwitchDetector* detector = (__bridge MMSoundSwitchDetector*)clientData;
[detector 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 {
_soundId = 0;
}
}
return self;
}
- (void)checkSoundSwitchStatus:(CheckSwitchStatusCompleteBlk)completionHandler {
if (self.soundId == 0) {
completionHandler(YES);
return;
}
self.completeHandler = completionHandler;
self.beginTime = CACurrentMediaTime();
AudioServicesPlaySystemSound(self.soundId);
}
- (void)complete {
CFTimeInterval elapsed = CACurrentMediaTime() - self.beginTime;
BOOL isSwitchOn = elapsed > 0.1;
if (self.completeHandler) {
self.completeHandler(isSwitchOn);
}
}4. Setting a Volume Threshold
Some users could not hear the reminder because system volume was too low. Directly setting AVAudioPlayer volume is ineffective because it is multiplied by the system volume. The solution mirrors the QR‑code screen‑brightness adjustment: if system volume is below a threshold, raise it temporarily, then restore the original level after playback.
Method 1: MPMusicPlayerController
MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer];
mpc.volume = 0; // 0.0~1.0 (deprecated after iOS 7)This method shows a system volume HUD, which may disturb users and is deprecated.
Method 2: MPVolumeView
Insert an invisible MPVolumeView, locate its MPVolumeSlider subview, and programmatically set its value, then send a touch‑up event. After adjusting, remove the view to avoid hiding future volume changes.
- (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];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}Tencent TDS Service
TDS Service offers client and web front‑end developers and operators an intelligent low‑code platform, cross‑platform development framework, universal release platform, runtime container engine, monitoring and analysis platform, and a security‑privacy compliance suite.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
