The background,

In order to solve the pain point that small business owners are not convenient to check and confirm the receipt of accounts in frequent transactions, product MM proposed a new version to support the voice reminder function of receipt of accounts. This article summarizes the pitfalls and tips encountered during development.

Ii. Technical scheme

Background Wake up App

The voice reminder of receiving money requires the receiver to play a TTS synthesized voice to broadcast the amount after receiving the money. When wechat is in the foreground, it can bring down the amount to be broadcast through the template message, and then request TTS data and play it. However, how can the APP request the voice data and play it when it is suspended or killed? IOS provides two ways to wake up suspended or killed apps. For Silent Notification and VoIP Push Notification respectively, the client will get 30 seconds of background running time after being woken up, which is long enough to request synthesized voice data and play it.

1.Silent Notification: Silent Notification is supported in iOS7 or later, but the number of times that Silent Notification can be pushed per hour is limited.

2. VoIP Push Notification: VoIP Push Notification is a new Push type that is only supported in iOS8. Compared with Silent Notification, VoIP Push has advantages of high priority and low latency, and there is no limit on the number of times.

Compared with these two technical solutions, VoIP Push Notification is obviously more suitable for the wake up scheme of receiving-to-account voice reminder.

TTS synthetic speech

TTS speech synthesis scheme is divided into off-line synthesis scheme and online synthesis scheme. The off-line synthesis scheme saves network request, and the synthesis speed is faster and network traffic is saved. However, the synthesized speech sounds mechanical and the processing of speed and pause is poor. If you don’t have a strong need for synthesized speech, consider using the AVSpeechSynthesis framework that comes with iOS to reduce the size of your speech library.

Online compositing was more vocal and emotional. Considering the product experience, we adopted the online speech synthesis scheme provided by the search Product Department. The access method can be seen in this article. The synthesized voice formats support WAV, MP3, Silk, AMR and Speex. After comparison, it is found that when the same text is synthesized, THE COMPRESSION rate of AMR is the highest, but the sound quality decreases obviously. The Silk format has the second highest compression rate and can maintain relatively clear sound quality. The size of a single synthesized voice is around 2KB.

Wake up and play the audio file

After requesting the synthesized voice, play the audio file in the background or on a locked screen, AVAudio Session Category of value need to use AVAudioSessionCategoryPlayback or AVAudioSessionCategoryPlayAndRecord, CategoryOptions Select MixWithOthers (mix with other sounds) or DuckOthers (lower the volume of other sounds) as required.

Note that only iOS10 and above supports playback of audio in background/locked screen after an app is woken up. Therefore, devices below iOS10 can only set a fixed ringtone on local Push after receiving VoIP Push, which is also why there is only “wechat Payment receivables received” below iOS10, but no specific amount value behind.

Three, mute switch detection

Unfortunately, the product was mocked by an Internet bigshot not long after its launch.

From the perspective of product experience, the announcement of the amount received to the account is played together with the pop-up of Local push, which is more like a special push ringtone. Apple’s handling of push ringtone is controlled by the mute switch, so this joke is reasonable. However, as mentioned earlier, after the App is awakened by VoIP Push, Need to set the AudioSessionCategory AVAudioSessionCategoryPlayback or AVAudioSessionCategoryPlayAndRecord can play audio files in the background, These two modes are not controlled by the mute switch. To do this, you must get the state of the current mute switch. Apple didn’t explicitly provide a way for developers to get the mute switch status after iOS5, which left them in an awkward position.

Apple can listen for the mute button in the following ways before 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; }Copy the code

Apple has banned listening for mute buttons in this way since iOS5. The reason behind this is that apple wants developers to use AVAudioSession to provide a unified audio playback effect.

In the end, I took to Reddit to find an easy way to save the country: Using AudioServicesPlaySystemSound play a period of 0.2 s blank audio, and events to monitor audio playback is complete, if from the start playing to the callback method of interval time is less than 0.1 s, means the mute switch is open.

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); }}Copy the code

Set the sound threshold

Another problem frequently reported by users was that they could not hear the broadcast sound. After checking the logs, it was found that the system volume set by users was too low when the voice broadcast was triggered. The first solution was to set the AVAudioPlayer volume (or the kAudioQueueParam_Volume in the AudioQueue) directly. However, this did not work. The volume property is subject to the system volume (e.g., the system volume is 0.5, AVAudioPlayer’s volume is 0.6, the final volume is 0.5*0.6 =0.3). To solve the problem of too small volume, or need to adjust the system volume. The final solution draws on the scheme that automatically adjusts the screen brightness when entering the QR code of payment display: if the screen brightness does not reach the threshold, adjust the screen brightness to the threshold, and when leaving the page, set the brightness back to the original brightness. Similarly, if the system volume is lower than the threshold, adjust it to the threshold. After the prompt tone is played, the tone is returned to the original volume.

There are two ways to control system volume:

Method 1: Set the volume through MPMusicPlayerController

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

The first method is simple and rude. The system volume prompt box will pop up when setting. If the volume prompt box suddenly pops up when users are using the app, it will cause trouble to users.

Method 2: Use MPVolumeView to set the volume

The second method is to add an invisible MPVolumeView to the current view and the system volume prompt will not be displayed.

Note That you need to remove MPVolumeView after adjusting the volume of the system. Otherwise, the system volume prompt box will not be displayed when you manually adjust the volume.

To adjust the volume, fetch a child View named MPVolumeSlider in MPVolumeView and send an event to it that simulates the user’s actions.

- (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]; / / by the send [volumeViewSlider sendActionsForControlEvents: UIControlEventTouchUpInside]; }}Copy the code


If you think our content is good, please forward it to moments and share it with your friends