在 iOS 开始在后台播放声音?

Start playing sound in background on iOS?

我正在尝试做类似于 Tile.app 的事情。当它显示通知时,它会播放声音。这看起来很简单——使用 UILocalNotification 并包含声音文件名。但是本地通知将声音限制在不超过 30 秒,而 Tile 的声音持续播放的时间比这长得多。我希望它会循环播放,并且会一直播放,直到我再也无法忍受噪音为止。

即使 phone 的静音开关打开,声音也会播放,本地通知不会发生这种情况。由于这些原因,UILocalNotification 似乎出局了。

我想也许我可以 post 纯文本本地通知并让我的应用程序播放声音。但是使用AVAudioPlayerplay方法returns NO,声音没有播放。

Other questions have suggested that this is by design, that apps aren't supposed to be able to play sounds from the background-- only continue sound that's already playing. I'd accept that reasoning except that Tile does it, so it's obviously possible. One suggested workaround 以某种方式违反了 Apple 的准则,我很确定 Apple 现在会检查。

一些可能相关的细节:

来自官方Apple documentation

"Custom sounds must be under 30 seconds when played. If a custom sound is over that limit, the default system sound is played instead."

您无法 UILocalNotication 循环播放声音。您可以以 30 秒的时间间隔再次安排它,但那将是另一个警报的安排。

要设置您的自定义通知声音:

notif.soundName = @"sound.caf";

确保声音确实在您的应用程序包中,格式正确(线性 PCM 或 IMA4——几乎所有解释如何为 iOS 转换声音的地方都会告诉您如何操作) , 不到 30 秒。

实际上您可以使用 AudioServices... 函数之一

  • AudioServicesPlaySystemSound
  • AudioServicesPlayAlertSound
  • AudioServicesPlaySystemSoundWithCompletion

开始在后台播放声音。

我不确定可以提交给 AudioServicesCreateSystemSoundID 的声音长度,因为我只用短循环声音检查铃声,但这个 API 与通知无关,所以可以立交桥 30 秒限制。

编辑: 应用程序仍需要 UIBackgroundModes 中的 audio 才能在后台播放

从我的测试项目appDelegate中截取,iOS9.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 回调中调用的,没有后台任务。