在iOS上如何在后台播放声音?

6

我正在尝试做类似于Tile.app的事情。当它显示通知时,会播放声音。这似乎很简单--使用UILocalNotification并包括声音文件名。但是本地通知将声音限制在不超过30秒,而Tile的声音持续时间远远超过了这个时间。我猜想它是循环播放的,并且一直持续到我无法忍受噪音为止。

即使手机静音,声音也会播放,这在本地通知中不会发生。因此,UILocalNotification看起来不适用。

我认为我可以发布一个仅文字的本地通知,并让我的应用程序播放声音。但是使用AVAudioPlayerplay方法返回NO,声音没有播放。

其他问题表明这是有意设计的,即应用程序不能从后台播放声音--只能继续播放已经播放的声音。我会接受那种说法,但Tile却可以做到这一点,所以显然是可能的。一个建议的解决方法以一种我相信Apple现在会检查的方式违反了苹果的指导方针。

一些可能相关的细节:

  • 我在Info.plist中使用了audio背景模式
  • 我在音频会话中使用AVAudioSessionCategoryPlayback,而且会话应该处于活动状态(至少setActive:error:声称成功)。
  • 此应用程序使用蓝牙,但目前不使用bluetooth-central背景模式。

Tile应用程序使用蓝牙,这是与音频不同的后台模式。有效地保持应用程序运行,虽然我自己没有这样做过,但我猜测您可以在该模式下播放任何声音,就好像应用程序在前台一样。 - shim
Tile使用蓝牙、音频和其他后台模式。其他的模式是否增加了像我需要的额外音频功能? - Tom Harrington
我说这可能是蓝牙模式让他们能够做到这一点。但仅有模式是不够的;大概需要连接到一个蓝牙外设,这会促使iOS启动与该外设相关联的应用程序。我没有使用过Tile,但看起来你需要在手机范围内按下Tile才能发出声音。 - shim
或者它可能使用后台位置更新功能始终保持唤醒状态。 - Xcoder
我在使用iBeacon感应技术时,没有任何问题可以在需要播放声音的背景下唤醒。问题在于如何播放声音,而不是在后台运行代码上。 - Tom Harrington
@TomHarrington 感谢,麻烦查看我的答案更新。 - sage444
2个回答

3

实际上您可以使用以下其中一种 AudioServices... 函数:

  • AudioServicesPlaySystemSound
  • AudioServicesPlayAlertSound
  • AudioServicesPlaySystemSoundWithCompletion

来开始在后台播放声音。

我不确定可以向 AudioServicesCreateSystemSoundID 提交多长时间的声音,因为我只测试了短暂循环的铃声,但此 API 不限于通知,所以可能可以超过 30 秒限制。

编辑: 应用程序仍需要在 UIBackgroundModes 中添加 audio 才能在后台播放音频。

下面是我测试项目的 appDelegate,iOS 9.3.1

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // test sound in foreground.
    registerAndPlaySound()

    return true
}

func applicationDidEnterBackground(application: UIApplication) {
    var task =  UIBackgroundTaskInvalid
    task = application.beginBackgroundTaskWithName("time for music") {
        application.endBackgroundTask(task)
    }
    // dispatch not required, it's just to simulate "deeper" background  
    let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * Double(NSEC_PER_SEC)))

    dispatch_after(delayTime, dispatch_get_main_queue()) {
        self.registerAndPlaySound()
    }
}

func registerAndPlaySound() {
    var soundId: SystemSoundID = 0

    let bundle = NSBundle.mainBundle()
    guard let soundUrl = bundle.URLForResource("InboundCallRing", withExtension: "wav") else {
        return
    }
    AudioServicesCreateSystemSoundID(soundUrl, &soundId)

    AudioServicesPlayAlertSound(soundId)
}

更新

在另一个类似的项目中,我使用了beginBackgroundTask来启动声音,作为在后台执行代码的最简单方法。类似于registerAndPlaySound的代码被从PushKit回调中调用,但并没有使用后台任务。


谢谢。我尝试了一下,当应用程序在前台时它可以工作。但是当在后台时声音不会播放。完成函数会立即被调用,但不幸的是这个函数没有错误状态参数。 - Tom Harrington
我一直在尝试自己解决这个问题。我们仍然存在一些在后台播放音频方面的问题。似乎是一些权限错误。 - Mike C.
是的,这些函数需要在后台模式下使用音频才能正常工作。 - sage444
谢谢!显然,后台任务技巧至关重要,而我之前没有这样做。我会接受这个答案,但如果您有任何关于为什么它很重要以及它的作用的信息/理论/猜测,我将不胜感激。 - Tom Harrington
谢谢。我试图做类似WhatsApp的视频通话通知,但无法在后台播放声音。你的技巧对我有用... - Leena
显示剩余3条评论

0

来自官方苹果文档

“播放自定义声音时,声音必须在30秒以内。如果自定义声音超过该限制,则播放默认系统声音。”

你无法让UILocalNotication循环播放声音。你可以按照30秒的时间间隔再次安排它,但那将是另一个闹钟的安排。

设置自定义通知声音:

notif.soundName = @"sound.caf";

确保声音实际上在您的应用程序包中,格式正确(线性PCM或IMA4 - 几乎任何解释如何将声音转换为iOS的地方都会告诉您如何做到这一点),并且不超过30秒。


为什么没有人投资这条评论! - Mohammad Bashir Sidani
3
谢谢,但是–正如我在问题中提到的–这个限制是UILocalNotification无法帮助解决问题的原因之一。我没有问题发布一个带有声音的本地通知,但它不能循环或播放超过30秒,这正是我试图解决的问题。加上静音开关打开时通知声音不会播放的问题。 - Tom Harrington

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接