iOS推送播放语音播报更新
更新
// The sound file to be played for the notification. The sound must be in the Library/Sounds folder of the app's data container or the Library/Sounds folder of an app group data container. If the file is not found in a container, the system will look in the app's bundle.
public convenience init(named name: UNNotificationSoundName)
APP 的文件夹 APP和 Extension共享Group的文件夹 App bundle
实现
下载到APP的Library/Sounds
推送时配置下载链接,在APP处理推送方法的地方,进行下载 单独接口配置下载链接,APP打开时调用,提前下载
APP和Extension共享Group的Library/Sounds文件夹
创建 APP 和 共享的 Group 在方法中,获取下载链接,并下载 下载成功后,存储到Group的文件夹下 存储成功后,播放
首先判断项目Bundle 中有没有音频文件,由于在中获取不到主项目的,所以需要在打开 APP 时,存储已存在的音频文件名字到共享,然后在中通过 Group 获取音频文件名字判断: // 存储已存在的音频文件名字到共享 Group, 在 App 打开时调用
static func updateAppMainBundleMP3FileResources() {
let path = Bundle.main.bundlePath
let fileManager = FileManager.default
do {
let allContentList = try fileManager.contentsOfDirectory(atPath: path)
let validContentList = allContentList.filter { $0.hasSuffix(".mp3") }
print(validContentList)let shareDefaults = UserDefaults(suiteName: shareDefaultsSuiteName())
shareDefaults?.setValue(validContentList, forKey: kMainAppMp3FileKey)
shareDefaults?.synchronize()
} catch {
print(error)
}
}
// 共享的Group
static func shareDefaultsSuiteName() -> String {
let name = "group.com.xxx.pushGroup"
return name
}判断APP 的下有没有对应音频文件,这一步需要考虑,是否采用了 APP提供单独接口下发语音文件链接,如果没有,则不需要考虑;笔者这里没有,所以不做详细演示。其逻辑大致如下:
获取项目文件夹 获取文件夹下所有音频文件 合并存储音频文件名字到共享 Group 中
fileprivate static let kMainAppMp3FileKey = "kMainAppMp3FileKey"
static func isVoiceInfoExist(voiceName: String) -> Bool {
/**
The /Library/Sounds directory of the app’s container directory.
The /Library/Sounds directory of one of the app’s shared group container directories.
The main bundle of the current executable.
*/
// 判断bundle中有没有
let soundStr = voiceName + ".mp3"let shareDefaults = UserDefaults(suiteName: shareDefaultsSuiteName())
if let validContentList = shareDefaults?.value(forKey: kMainAppMp3FileKey) as? [String],
validContentList.contains(soundStr) {
// 文件存在
return true
}// 判断 /Library/Sounds 文件夹下有没有
let fileManager = FileManager.default
if let soundsDirectoryURL = getLibrarySoundsDir() {
let filePath = (soundsDirectoryURL as NSString).appendingPathComponent(voiceName + ".mp3")
print("------", filePath)
if fileManager.fileExists(atPath: filePath) {
return true
}
}// 文件不存在存在
return false
}// 获取共享 Group 的`Library/Sounds`文件夹
static func getLibrarySoundsDir() -> String? {
let fileManager = FileManager.default
let groupIdentifer = shareDefaultsSuiteName()
let sharedContainerURL: URL? = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifer)
if let soundsDirectoryURL = sharedContainerURL?.appendingPathComponent("Library/Sounds") {
let fileExist = fileManager.fileExists(atPath: soundsDirectoryURL.path)
if !fileExist {
do {
try fileManager.createDirectory(atPath: soundsDirectoryURL.path,
withIntermediateDirectories: true)
} catch {
print(error)
}
}
return soundsDirectoryURL.path
}
return nil
}
// 下载音频文件
static func downloadAndSave(url: URL, voiceName: String, handler: @escaping (_ localURL: URL?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, res, error in
var localURL: URL?
if let data = data {
let librarySoundDir = getLibrarySoundsDir()
let filePath = (librarySoundDir as? NSString)?.appendingPathComponent(voiceName + ".mp3")
if let urlStr = filePath {
let targetUrl = URL(fileURLWithPath: urlStr)
do {
_ = try data.write(to: targetUrl)
} catch {
print(error)
}
print("url------", targetUrl)
localURL = targetUrl
}
}
handler(localURL)
}
task.resume()
}
import UserNotifications
import AVFoundation
class NotificationServiceUtil {
func playVoice(with bestAttemptContent: UNMutableNotificationContent, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
let userInfo = bestAttemptContent.userInfodo {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}// 要播放的语音文件名字
guard let voiceName = userInfo["voiceName"] as? String else {
contentHandler(bestAttemptContent)
return
}// 判断本地是否有语音文件, 有则播放; 没有则下载或者尝试系统语音播放
let isVoiceExists = NotificationServiceUtil.isVoiceInfoExist(voiceName: voiceName)
let soundStr = voiceName + ".mp3"
let soundName = UNNotificationSoundName(soundStr)if isVoiceExists {
bestAttemptContent.sound = UNNotificationSound(named: soundName)
contentHandler(bestAttemptContent)
} else {
// 下载链接
if let voiceUrlStr = userInfo["voiceUrl"] as? String,
let voiceUrlUrl = URL(string: voiceUrlStr) {
// 下载
NotificationServiceUtil.downloadAndSave(url: voiceUrlUrl, voiceName: voiceName) { localURL in
bestAttemptContent.sound = UNNotificationSound(named: soundName)
contentHandler(bestAttemptContent)
}
} else {
contentHandler(bestAttemptContent)
}
}
}
}
import UserNotifications
class NotificationService: UNNotificationServiceExtension {var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
fileprivate lazy var util = NotificationServiceUtil()override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)if let bestAttemptContent = bestAttemptContent {
// 播放处理
util.playVoice(with: bestAttemptContent, withContentHandler: contentHandler)
}
}override func 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.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}