今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

How to enable voice playback for iOS push notifications

This article was published on the "Sohu Technology Products" public account How to Play Voice Notifications on iOS

iOS Voice Notification Playback#

1. Background#

The demand for iOS voice notification playback involves playing the content of the notification upon receipt, with the content being variable. This is similar to the voice notifications for payment receipts in Alipay and WeChat.

2. Development Process#

a. Notification Service Extension#

1342050-74642172d12a47b5.png

The logic after adding the Notification Service Extension to the project differs from before. As shown below:
After adding it, when a push notification is received, it will trigger a method in the Notification Service Extension, where you can modify the notification's title, content, and sound. Then, display the modified notification.

The lifecycle of the notification bar:

  • From the moment the notification is displayed (trigger code: self.contentHandler(self.bestAttemptContent);) until it is dismissed (controlled by the system), there is approximately 6 seconds.
  • If the notification is received but the notification bar is not called out, the system will call the self.contentHandler(self.bestAttemptContent) in the serviceExtensionTimeWillExpire method after a maximum of 30 seconds to bring up the notification bar.

It is important to note that the Notification Service Extension and the main project are not the same target, so the files of the main project are not shared with this target.

  • When creating new files, be sure to check the target to which they should be added.
    • For example, when adding a class for playing voice notifications, it should be checked under the Notification Service Extension target;
    • When copying a third-party SDK for voice playback, it should also be checked under the Notification Service Extension target;
    • When creating a new application on the third-party platform, the bundle ID to be filled in should correspond to the bundle ID of the Notification Service Extension target. This is particularly important because the Baidu test account can only add the offline SDK once; if you make a mistake, you will need to register with a new account, a painful lesson, 😂.
  • The bundle directory access is also not the same, but data can be shared through App Group.
  • When enabling background playback, it should actually be the background playback of the Notification Service Extension target, which will be explained in detail later.

The creation steps are as follows:

  • Create a Notification Service Extension target by selecting the Xcode project, clicking File -> New -> Target, and selecting Notification Service Extension target. There are two very similar options, so be careful to select the correct one, as shown below:
    截屏 2021-04-13 下午 3.01.00.png

  • Click Next, enter the Product Name
    截屏 2021-04-13 下午 3.05.43.png

  • Click Finish, then click Activate
    截屏 2021-04-13 下午 3.05.51.png

  • Open the NotificationService.m file, which is the class automatically created after adding the Notification Service Extension. After adding it, all processing for received pushes can be modified in this location.

    • In the didReceiveNotificationRequest:withContentHandler: method, the userInfo in bestAttemptContent contains the detailed information of the push notification. If you want to modify the displayed title and content or the voice of the push, all operations should be done before calling back at the end of this method.
      • When modifying the notification sound, note that:
      • For handling multiple pushes, call self.contentHandler(self.bestAttemptContent); in the didReceiveNotificationRequest:withContentHandler: method to display the corresponding notification. If this method is not called, the system will automatically call this method after a maximum of 30 seconds. If 10 notifications come in at once, you will find that the notifications do not pop up 10 times and are not displayed in order. Therefore, if multiple pushes are not handled, there will be issues when playing the voice.
        • In the delegate method of the AVSpeechSynthesizer class, there is a completion callback speechSynthesizer:didFinishSpeechUtterance:. Move the code to call self.contentHandler(self.bestAttemptContent) from the didReceiveNotificationRequest:withContentHandler: method to the completion callback method to ensure that the voices are displayed in order. (Alternatively, add them to an array or an OperationQueue, and continue to the next one after the playback is complete.)

In the AppleTtsUtils implementation, it is roughly using AVSpeechSynthesizer for direct playback, setting volume and speech rate. Note that:

b. Adding Baidu TTS Offline SDK#

  1. Open the Baidu Smart Console, select the application list, create a new application for testing, and after creation, ensure that the bundle ID matches the bundle ID of the Notification Service Extension created, not the main project bundle ID. Be careful!!! As shown below:

    1618303510485.jpg

  2. Select Offline SDK Management on the left, click Add, then select the newly created application, click Finish, and download the serial number list. Store the AppId, AppKey, SecretKey, and serial number for initializing the offline SDK. As shown below:

    1618303458956.jpg

  3. When selecting Offline SDK Management on the left, click the right side to download the SDK and the Development Documentation. According to the SDK's instructions:

    Integration Guide: It is strongly recommended that users first run the Demo project in the SDK package, which details the usage of speech synthesis and provides complete examples. Generally, you only need to refer to the demo project to complete all integration and configuration work.

  4. After downloading the SDK, open the BDSClientSample project, change the APP_ID, API_KEY, SECRET_KEY, and SN in the TTSViewController.mm file to the ones just applied for, and run the test to see if the voice playback works normally. Successful playback indicates that the application is fine, and you can continue integrating into the project; otherwise, if it does not play after integration, you might suspect it is an SDK issue. 😂 Debugging after integration can indeed make one question life.

  5. Drag the BDSClientHeaders, BDSClientLib, and BDSClientResource folders extracted from the SDK into the Notification Service Extension target, ensuring to check the copy option. Then delete the .gitignore file in the BDSClientLib folder; otherwise, the compilation will fail, I’m not kidding, 😂, a guide to avoid pitfalls.
    1618304109702.jpg

  6. Add the required system libraries, referring to the dependencies in the BDSClientSample project, and ensure they are added to the Notification Service Extension target, as shown below:
    1618304468870.jpg

  7. Done, compile the Notification Service Extension target, ensuring to select the correct target. Oh, there is another issue here; the newly created target is based on the version of Xcode, so you also need to modify the minimum target compatibility of this target; otherwise, the default might be 14.4. It will run normally without errors, but breakpoints won’t hit, surprising, 😂.
    1618304749612.jpg

  8. Add the Baidu voice processing code to the Notification Service Extension target as described above. The BaiDuTtsUtils code is as follows:

    • Note that in the configureOfflineTTS method, the loading of offlineSpeechData and offlineTextData resources should be consistent with what is written in the Demo; it is actually the content in the TTS folder of the BDSClientResource folder. If you have downloaded other voice files, load your downloaded voice files here.

c. Debugging#

The exciting part comes now. If everything compiled without issues, use push notifications for debugging. First, run the main project, then select the Notification Service Extension target to run, set breakpoints in the didReceiveNotificationRequest:withContentHandler: method, and send yourself a push message. You will find that the breakpoint hits here, indicating that the target was created correctly.

Then control the push parameters, isRead and isBaiDu, which determine whether the voice playback of the push notification uses Baidu's voice. Oh, speaking of push parameters, you also need to add the "mutable-content = 1" field in the payload push parameters, e.g.:

During push debugging, you may find that it runs normally, but the voice does not play, whether it is the system or Baidu's. Haha, frustrating, right? Upon closely examining the console, you may find the following error:

Ps: After iOS 12.0, calling system playback with AVSpeechSynthesizer in Notification Service Extension results in the following error.

Ps: After iOS 12.0, calling Baidu's SDK for direct playback in Notification Service Extension results in the following error.

Both errors indicate that audio cannot be played in the background. How to solve this? Of course, by adding the background mode field. Open the main project's Signing & Capabilities, add Background Modes, and check Audio, Airplay, and Picture in Picture, as shown below:
1618306139128.jpg
1618306179927.jpg

OK, try again! After pushing again, you will find — still not working, same error, haha, despair, right? Sorry, let me calm down. Actually, the addition was correct, but you need to note:

  1. After configuring the Notification Service Extension, if you find that the sound still does not play after receiving the notification, open the plist under this Extension's Target, add the Required background modes field, and write "App plays audio or streams audio/video using AirPlay" in item0. After debugging again, you will find that Baidu's voice can be played.
  2. This method may not pass the review because this Extension's target does not actually have background mode settings, as can be seen from Signing & Capabilities. Therefore, if it is not going to be published on the App Store, but just for internal distribution within the company, this method can be used.

After adding it, when you push again, you will find that Baidu's voice can be played, and the playback of numbers, English, and Chinese is perfect, except for the pricing being a bit concerning, everything else is fine.
As for the system's voice playback, if you push the system's first, you will find it cannot play, still the same error; however, if you first push Baidu's, and after Baidu plays, then push the system's, you will find that the system's can also report. But the system's playback of English and numbers may have issues, remember to handle it; you can listen to the pronunciation of the English letter E, the pronunciation is... The solution — none yet found, it is recommended to use third-party synthesized voices.

Since the project does not need to be published to the App Store, this is where it ends. However, for applications that will be published to the App Store, this method is not feasible. Applications that are published to the App Store can only use fixed format audio playback as a solution, i.e., replacing the notification sound. Using fixed format audio or fixed format synthesized audio to replace the notification sound, or using remote push to mute and sending multiple local notifications, each with different sounds, are insights gained from the references at the end.

3. Conclusion#

Directly presenting the organized mind map below, most of the more complex processing logic is actually the handling after iOS 12.0.
推送播放语音.png

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.