iOS: 播放来自通知扩展的远程音频文件
iOS: play remote audio file from notification extension
我正在尝试从通知扩展播放远程音频文件
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
//...
let url = URL(string: "https://firebasestorage.googleapis.com/v0/b/XXX.appspot.com/o/XXXXXXX?alt=media&token=XXXXXXX")
let item = AVPlayerItem(url: url!)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
let player = AVPlayer(playerItem: item)
player.play()
}
但是没有任何反应。
在应用程序的功能中,我启用了以下后台模式:"Audio, AirPlay, and PiP"、"Background fetch"、"Remote notifications"
我需要在没有用户交互的情况下收到通知时播放声音。
此外,媒体附件不是一个选项,因为 iOS 10 支持是强制性的。
UPD:我已经试过了
func download(url: URL) {
URLSession.shared.downloadTask(with: url, completionHandler: { _, _, error in
if let error = error {
print(error)
}
self.play(url: url)
}).resume()
}
func play(url: URL) {
let player = try! AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
player.volume = 1.0
player.play()
}
也什么都没发生
我认为,根据我之前的经验,我们可以在通知到达时播放捆绑声音文件。这意味着,我们必须将声音文件放在应用程序包中,并且声音文件是应用程序的预定义资源。
如果您想播放不同的声音,请将所有声音文件添加到应用程序包中,然后 Identify/Play 使用通知有效负载密钥。
例如,如果你想播放带有相关通知的 ABC 文件,则使用通知有效负载设置适当的 logic/Key 并识别它。
请告诉我您的想法。快乐编码:)
其他参考:
Play notification sound using URL
如果您想使用 URL 播放声音。然后你可以下载这个文件或者你可以缓冲特定文件后播放:
现在可以使用 iOS 13。我会在此处复制消息的内容,以防它被删除。 https://forums.developer.apple.com/thread/118404Correct
Answer by KevinE on Jul 30, 2019 5:11 PM
The specifics of how your app should work are going to vary a lot
depending on your specific use cases and app design, but our general
recommendation for push to talk apps is that you shift to using
standard push notifications instead of PushKit for message delivery.
More specifically, on the receiver side your Notification Service
Extension should download the relevant audio and attach that sound to
your particular message.
To support that approach, starting in iOS 13, the system looks for
possible sound files in the apps group container(s) as well as the
preexisting search locations. The documentation hasn't been updated
for this yet (), but there is header file documentation describing the
details in the comments for "soundNamed:" in "UNNotificationSound.h":
// 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.
Breaking that down in detail, we look for sound files in the following
order and locations:
Your apps direct container in "Library/Sounds". Your apps group(s)
directory in a directory named "Library/Sounds" Your apps bundle The
main things to keep in mind here:
Directory #2 is "Library/Sounds", not just "Sounds". You'll need to
create a "Library" directory with a "Sounds" directory inside it, not
just a "Sounds" directory. The apps direct container is still the
first place we'll look, so you'll need to be careful about how you
name your files. I would recommend either using the group directory
for all sounds or following a naming convention for the two different
locations so that they never collide. Your Network Service Extension
can't see the contents of directory #1, so it can't know whether or
not a particular name will collide.
感谢 TALE 的回答,我已经实现了它
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.receivedRequest = request;
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
if (request.content.userInfo != nil && request.content.userInfo[@"audioUrl"] != nil) {
NSURL* audioUrl = [NSURL URLWithString:request.content.userInfo[@"audioUrl"]];
NSString* audioName = audioUrl.lastPathComponent;
[[[NSURLSession sharedSession] downloadTaskWithURL:audioUrl completionHandler: ^void (NSURL* location, NSURLResponse* response, NSError* error) {
if (location == nil) {
contentHandler(self.bestAttemptContent);
NSLog(@"%@", error);
return;
}
NSFileManager* fileMan = [NSFileManager defaultManager];
NSString* containerPath = [fileMan containerURLForSecurityApplicationGroupIdentifier:@"group.com.example"].path; // Put your app group id here
NSString* soundDir = [containerPath stringByAppendingString:@"/Library/Sounds"];
NSError* errorOut;
if (![fileMan fileExistsAtPath:soundDir]) {
[fileMan createDirectoryAtPath:soundDir withIntermediateDirectories:YES attributes:nil error:&errorOut];
} else {
// Delete old files
NSArray<NSString*>* oldFiles = [fileMan contentsOfDirectoryAtPath:soundDir error:&errorOut];
if (oldFiles != nil) {
for (NSString* oldFileName in oldFiles) {
NSString* oldFilePath = [soundDir stringByAppendingString:oldFileName];
[fileMan removeItemAtPath:oldFilePath error:&errorOut];
}
}
}
NSString* soundPath = [soundDir stringByAppendingString:[@"/" stringByAppendingString:audioName]];
NSURL* soundUrl = [NSURL fileURLWithPath:soundPath];
[fileMan copyItemAtURL:location toURL:soundUrl error:&errorOut];
UNNotificationSound* sound = [UNNotificationSound soundNamed:audioName];
self.bestAttemptContent.sound = sound;
contentHandler(self.bestAttemptContent);
}] resume];
return;
}
// Your logic for non-audio notification
self.contentHandler(self.bestAttemptContent);
}
如果您需要 Swift 版本,请随时发表评论
我正在尝试从通知扩展播放远程音频文件
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
//...
let url = URL(string: "https://firebasestorage.googleapis.com/v0/b/XXX.appspot.com/o/XXXXXXX?alt=media&token=XXXXXXX")
let item = AVPlayerItem(url: url!)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
let player = AVPlayer(playerItem: item)
player.play()
}
但是没有任何反应。
在应用程序的功能中,我启用了以下后台模式:"Audio, AirPlay, and PiP"、"Background fetch"、"Remote notifications"
我需要在没有用户交互的情况下收到通知时播放声音。 此外,媒体附件不是一个选项,因为 iOS 10 支持是强制性的。
UPD:我已经试过了
func download(url: URL) {
URLSession.shared.downloadTask(with: url, completionHandler: { _, _, error in
if let error = error {
print(error)
}
self.play(url: url)
}).resume()
}
func play(url: URL) {
let player = try! AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
player.volume = 1.0
player.play()
}
也什么都没发生
我认为,根据我之前的经验,我们可以在通知到达时播放捆绑声音文件。这意味着,我们必须将声音文件放在应用程序包中,并且声音文件是应用程序的预定义资源。
如果您想播放不同的声音,请将所有声音文件添加到应用程序包中,然后 Identify/Play 使用通知有效负载密钥。
例如,如果你想播放带有相关通知的 ABC 文件,则使用通知有效负载设置适当的 logic/Key 并识别它。
请告诉我您的想法。快乐编码:)
其他参考: Play notification sound using URL
如果您想使用 URL 播放声音。然后你可以下载这个文件或者你可以缓冲特定文件后播放:
现在可以使用 iOS 13。我会在此处复制消息的内容,以防它被删除。 https://forums.developer.apple.com/thread/118404Correct
Answer by KevinE on Jul 30, 2019 5:11 PM
The specifics of how your app should work are going to vary a lot depending on your specific use cases and app design, but our general recommendation for push to talk apps is that you shift to using standard push notifications instead of PushKit for message delivery. More specifically, on the receiver side your Notification Service Extension should download the relevant audio and attach that sound to your particular message.
To support that approach, starting in iOS 13, the system looks for possible sound files in the apps group container(s) as well as the preexisting search locations. The documentation hasn't been updated for this yet (), but there is header file documentation describing the details in the comments for "soundNamed:" in "UNNotificationSound.h":
// 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.
Breaking that down in detail, we look for sound files in the following order and locations:
Your apps direct container in "Library/Sounds". Your apps group(s) directory in a directory named "Library/Sounds" Your apps bundle The main things to keep in mind here:
Directory #2 is "Library/Sounds", not just "Sounds". You'll need to create a "Library" directory with a "Sounds" directory inside it, not just a "Sounds" directory. The apps direct container is still the first place we'll look, so you'll need to be careful about how you name your files. I would recommend either using the group directory for all sounds or following a naming convention for the two different locations so that they never collide. Your Network Service Extension can't see the contents of directory #1, so it can't know whether or not a particular name will collide.
感谢 TALE 的回答,我已经实现了它
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.receivedRequest = request;
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
if (request.content.userInfo != nil && request.content.userInfo[@"audioUrl"] != nil) {
NSURL* audioUrl = [NSURL URLWithString:request.content.userInfo[@"audioUrl"]];
NSString* audioName = audioUrl.lastPathComponent;
[[[NSURLSession sharedSession] downloadTaskWithURL:audioUrl completionHandler: ^void (NSURL* location, NSURLResponse* response, NSError* error) {
if (location == nil) {
contentHandler(self.bestAttemptContent);
NSLog(@"%@", error);
return;
}
NSFileManager* fileMan = [NSFileManager defaultManager];
NSString* containerPath = [fileMan containerURLForSecurityApplicationGroupIdentifier:@"group.com.example"].path; // Put your app group id here
NSString* soundDir = [containerPath stringByAppendingString:@"/Library/Sounds"];
NSError* errorOut;
if (![fileMan fileExistsAtPath:soundDir]) {
[fileMan createDirectoryAtPath:soundDir withIntermediateDirectories:YES attributes:nil error:&errorOut];
} else {
// Delete old files
NSArray<NSString*>* oldFiles = [fileMan contentsOfDirectoryAtPath:soundDir error:&errorOut];
if (oldFiles != nil) {
for (NSString* oldFileName in oldFiles) {
NSString* oldFilePath = [soundDir stringByAppendingString:oldFileName];
[fileMan removeItemAtPath:oldFilePath error:&errorOut];
}
}
}
NSString* soundPath = [soundDir stringByAppendingString:[@"/" stringByAppendingString:audioName]];
NSURL* soundUrl = [NSURL fileURLWithPath:soundPath];
[fileMan copyItemAtURL:location toURL:soundUrl error:&errorOut];
UNNotificationSound* sound = [UNNotificationSound soundNamed:audioName];
self.bestAttemptContent.sound = sound;
contentHandler(self.bestAttemptContent);
}] resume];
return;
}
// Your logic for non-audio notification
self.contentHandler(self.bestAttemptContent);
}
如果您需要 Swift 版本,请随时发表评论