如何中断设备音频播放声音,然后让设备音频继续 iOS
How to interrupt device audio to play a sound and then let the device audio continue on iOS
我有一个应用程序,用户可以在其中播放从其他用户收到的语音消息。播放语音消息应中断设备音频(音乐、播客等从其他应用程序播放),播放语音消息然后让设备音频继续。
这是我正在尝试实现的特定用例
- 用户开始通过 Apple Music 在设备上播放音乐
- 用户打开应用并点击语音消息
- Apple Music 停止
- 应用程序中播放语音消息
- Apple Music 继续播放
通过将 AVAudioSession
的类别设置为 .ambient
,我可以在播放 Apple Music 的同时播放语音消息,但这并不是我所需要的。
如果我使用使 Apple Music 停止的 .playback
类别,会在应用程序中播放语音消息,但 Apple Music 之后不会继续播放。
您已经发现可以通过激活 .playback
类别音频会话来中断其他音频应用程序。当您完成播放音频并希望中断的音频继续播放时,停用音频会话并传递 notifyOthersOnDeactivation
选项。
例如
try! audioSession.setActive(false, options: .notifyOthersOnDeactivation)
我认为那些应该继续运行的应用程序,如 Apple Music、Spotify、Radio 应用程序等,实现了处理中断的功能,以及当另一个应用程序的音频被停用/想要交还音频责任时的功能。
那么你能不能试试看这是否有效
// I play the audio using this AVAudioPlayer
var player: AVAudioPlayer?
// Implement playing a sound
func playSound() {
// Local url for me, but you could configure as you need
guard let url = Bundle.main.url(forResource: "se_1",
withExtension: "wav")
else { return }
do {
// Set the category to playback to interrupt the current audio
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
// This is to know when the sound has ended
player?.delegate = self
player?.play()
} catch let error {
print(error.localizedDescription)
}
}
extension AVAudioInterruptVC: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool) {
do {
// When the sound has ended, notify other apps
// You can do this where ever you want, I just show the
// example of doing it when the audio has ended
try AVAudioSession.sharedInstance()
.setActive(false, options: [.notifyOthersOnDeactivation])
}
catch {
print(error)
}
}
}
理论上,Apple 提供了中断和恢复背景音频的“协议”,在一个可下载的示例中,我向您展示它是什么并证明它有效:
在该示例中,有 两个 项目,代表两个不同的应用程序。你 运行 他们 同时。 BackgroundPlayer 在后台播放声音; Interrupter 中断它,暂停它,当它完成中断时,BackgroundPlayer 恢复。
正如您将看到的,这是通过让 Interrupter 在中断时将其音频会话类别从环境音更改为回放,然后将其改回 完成后,首先在发送 .notifyOthersOnDeactivation
信号时完全停用自身:
func playFile(atPath path:String) {
self.player?.delegate = nil
self.player?.stop()
let fileURL = URL(fileURLWithPath: path)
guard let p = try? AVAudioPlayer(contentsOf: fileURL) else {return} // nicer
self.player = p
// error-checking omitted
// switch to playback category while playing, interrupt background audio
try? AVAudioSession.sharedInstance().setCategory(.playback, mode:.default)
try? AVAudioSession.sharedInstance().setActive(true)
self.player.prepareToPlay()
self.player.delegate = self
let ok = self.player.play()
print("interrupter trying to play \(path): \(ok)")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // *
let sess = AVAudioSession.sharedInstance()
// this is the key move
try? sess.setActive(false, options: .notifyOthersOnDeactivation)
// now go back to ambient
try? sess.setCategory(.ambient, mode:.default)
try? sess.setActive(true)
delegate?.soundFinished(self)
}
然而,问题在于对 .notifyOthersOnDeactivation
的响应完全取决于其他应用程序是否表现良好。我的另一个应用程序 BackgroundPlayer 表现良好。这是它的作用:
self.observer = NotificationCenter.default.addObserver(forName:
AVAudioSession.interruptionNotification, object: nil, queue: nil) {
[weak self] n in
guard let self = self else { return } // legal in Swift 4.2
let why = n.userInfo![AVAudioSessionInterruptionTypeKey] as! UInt
let type = AVAudioSession.InterruptionType(rawValue: why)!
switch type {
case .began:
print("interruption began:\n\(n.userInfo!)")
case .ended:
print("interruption ended:\n\(n.userInfo!)")
guard let opt = n.userInfo![AVAudioSessionInterruptionOptionKey] as? UInt else {return}
let opts = AVAudioSession.InterruptionOptions(rawValue: opt)
if opts.contains(.shouldResume) {
print("should resume")
self.player.prepareToPlay()
let ok = self.player.play()
print("bp tried to resume play: did I? \(ok as Any)")
} else {
print("not should resume")
}
@unknown default:
fatalError()
}
}
如你所见,我们注册中断通知,如果我们被中断,我们寻找.shouldResume
选项——这是中断的结果首先设置 notifyOthersOnDeactivation
。
到目前为止,还不错。但是有一个障碍。某些应用程序在这方面表现不佳。而最 non-well-behaved 是苹果自己的音乐应用程序!因此,实际上 不可能 让音乐应用程序执行您希望它执行的操作。你最好使用ducking,系统只是为你调整两个应用程序的相对电平,让后台应用程序(音乐)继续播放,但更安静。
我有一个应用程序,用户可以在其中播放从其他用户收到的语音消息。播放语音消息应中断设备音频(音乐、播客等从其他应用程序播放),播放语音消息然后让设备音频继续。
这是我正在尝试实现的特定用例
- 用户开始通过 Apple Music 在设备上播放音乐
- 用户打开应用并点击语音消息
- Apple Music 停止
- 应用程序中播放语音消息
- Apple Music 继续播放
通过将 AVAudioSession
的类别设置为 .ambient
,我可以在播放 Apple Music 的同时播放语音消息,但这并不是我所需要的。
如果我使用使 Apple Music 停止的 .playback
类别,会在应用程序中播放语音消息,但 Apple Music 之后不会继续播放。
您已经发现可以通过激活 .playback
类别音频会话来中断其他音频应用程序。当您完成播放音频并希望中断的音频继续播放时,停用音频会话并传递 notifyOthersOnDeactivation
选项。
例如
try! audioSession.setActive(false, options: .notifyOthersOnDeactivation)
我认为那些应该继续运行的应用程序,如 Apple Music、Spotify、Radio 应用程序等,实现了处理中断的功能,以及当另一个应用程序的音频被停用/想要交还音频责任时的功能。
那么你能不能试试看这是否有效
// I play the audio using this AVAudioPlayer
var player: AVAudioPlayer?
// Implement playing a sound
func playSound() {
// Local url for me, but you could configure as you need
guard let url = Bundle.main.url(forResource: "se_1",
withExtension: "wav")
else { return }
do {
// Set the category to playback to interrupt the current audio
try AVAudioSession.sharedInstance().setCategory(.playback,
mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
// This is to know when the sound has ended
player?.delegate = self
player?.play()
} catch let error {
print(error.localizedDescription)
}
}
extension AVAudioInterruptVC: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
successfully flag: Bool) {
do {
// When the sound has ended, notify other apps
// You can do this where ever you want, I just show the
// example of doing it when the audio has ended
try AVAudioSession.sharedInstance()
.setActive(false, options: [.notifyOthersOnDeactivation])
}
catch {
print(error)
}
}
}
理论上,Apple 提供了中断和恢复背景音频的“协议”,在一个可下载的示例中,我向您展示它是什么并证明它有效:
在该示例中,有 两个 项目,代表两个不同的应用程序。你 运行 他们 同时。 BackgroundPlayer 在后台播放声音; Interrupter 中断它,暂停它,当它完成中断时,BackgroundPlayer 恢复。
正如您将看到的,这是通过让 Interrupter 在中断时将其音频会话类别从环境音更改为回放,然后将其改回 完成后,首先在发送 .notifyOthersOnDeactivation
信号时完全停用自身:
func playFile(atPath path:String) {
self.player?.delegate = nil
self.player?.stop()
let fileURL = URL(fileURLWithPath: path)
guard let p = try? AVAudioPlayer(contentsOf: fileURL) else {return} // nicer
self.player = p
// error-checking omitted
// switch to playback category while playing, interrupt background audio
try? AVAudioSession.sharedInstance().setCategory(.playback, mode:.default)
try? AVAudioSession.sharedInstance().setActive(true)
self.player.prepareToPlay()
self.player.delegate = self
let ok = self.player.play()
print("interrupter trying to play \(path): \(ok)")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { // *
let sess = AVAudioSession.sharedInstance()
// this is the key move
try? sess.setActive(false, options: .notifyOthersOnDeactivation)
// now go back to ambient
try? sess.setCategory(.ambient, mode:.default)
try? sess.setActive(true)
delegate?.soundFinished(self)
}
然而,问题在于对 .notifyOthersOnDeactivation
的响应完全取决于其他应用程序是否表现良好。我的另一个应用程序 BackgroundPlayer 表现良好。这是它的作用:
self.observer = NotificationCenter.default.addObserver(forName:
AVAudioSession.interruptionNotification, object: nil, queue: nil) {
[weak self] n in
guard let self = self else { return } // legal in Swift 4.2
let why = n.userInfo![AVAudioSessionInterruptionTypeKey] as! UInt
let type = AVAudioSession.InterruptionType(rawValue: why)!
switch type {
case .began:
print("interruption began:\n\(n.userInfo!)")
case .ended:
print("interruption ended:\n\(n.userInfo!)")
guard let opt = n.userInfo![AVAudioSessionInterruptionOptionKey] as? UInt else {return}
let opts = AVAudioSession.InterruptionOptions(rawValue: opt)
if opts.contains(.shouldResume) {
print("should resume")
self.player.prepareToPlay()
let ok = self.player.play()
print("bp tried to resume play: did I? \(ok as Any)")
} else {
print("not should resume")
}
@unknown default:
fatalError()
}
}
如你所见,我们注册中断通知,如果我们被中断,我们寻找.shouldResume
选项——这是中断的结果首先设置 notifyOthersOnDeactivation
。
到目前为止,还不错。但是有一个障碍。某些应用程序在这方面表现不佳。而最 non-well-behaved 是苹果自己的音乐应用程序!因此,实际上 不可能 让音乐应用程序执行您希望它执行的操作。你最好使用ducking,系统只是为你调整两个应用程序的相对电平,让后台应用程序(音乐)继续播放,但更安静。