How to Allow Background Music to Continue Playing While My App Still Plays Its Sounds While Using Swift

How can I allow background music to continue playing while my app still plays its sounds while using Swift

You need to set the AVAudioSession category, with one of the following value: https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioSession_ClassReference/index.html (AVAudioSession Class Reference).

The default value is set to AVAudioSessionCategorySoloAmbient. As you can read :

[...] using this category implies that your app’s audio is nonmixable—activating your session will interrupt any other audio sessions which are also nonmixable. To allow mixing, use the AVAudioSessionCategoryAmbient category instead.

You have to change the category, before you play your sound. To do so :

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)

You don't need to call those line each time you play the sound. You might want to do it only once.

How to play audio in background with Swift?

You need to set your app Capabilities Background Modes (Audio and AirPlay) and set your AVAudioSession category to AVAudioSessionCategoryPlayback and set it active

From Xcode 11.4 • Swift 5.2

do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}

Sample Image

How to play sounds in iOS 10 when app is in the background with Swift 3

You cannot play in the background with an Ambient audio session category. Your category must be Playback.

To allow sounds from other apps to play, modify your Playback category with the Mixable option (.mixWithOthers).

(Also keep in mind that you can change your category at any time. You could have it be Ambient most of the time but switch to Playback when you know you're about to go into the background so that you can keep playing.)

EDIT Also it occurs to me that there is another possible misconception that might need clearing up. You cannot (easily) start a new sound while in the background. All that background audio allows you to do is continue playing the current sound when your app goes into the background. Once that sound stops (in the background), your app suspends and that's the end of that.

How to make background sound quiter

This is a feature called audio ducking- the sound that gets quieter is said to be ducked. The way Android does it is through audio focus. Your app needs to request focus, and the other app's sound will be changed appropriately. The documentation on it is at https://developer.android.com/guide/topics/media-apps/audio-focus. The mechanism changed in Android 12, so you'll likely have to have different code for older versions and newer ones to make it all work.

How to interrupt device audio to play a sound and then let the device audio continue on iOS

In theory, Apple has provided a "protocol" for interrupting and resuming background audio, and in a downloadable example, I show you what it is and prove that it works:

https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/bk2ch14p653backgroundPlayerAndInterrupter

In that example, there are two projects, representing two different apps. You run both of them simultaneously. BackgroundPlayer plays sound in the background; Interrupter interrupts it, pausing it, and when it is finished interrupting, BackgroundPlayer resumes.

This, as you will see, is done by having Interrupter change its audio session category from ambient to playback while interrupting, and changing it back when finished, along with first deactivating itself entirely while sending the .notifyOthersOnDeactivation signal:

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)
}

The trouble, however, is that response to .notifyOthersOnDeactivation is entirely dependent on the other app being well behaved. My other app, BackgroundPlayer, is well behaved. This is what it does:

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()
}
}

As you can see, we register for interruption notifications, and if we are interrupted, we look for the .shouldResume option — which is the result of the interrupter setting the notifyOthersOnDeactivation in the first place.

So far, so good. But there's a snag. Some apps are not well behaved in this regard. And the most non-well-behaved is Apple's own Music app! Thus it is actually impossible to get the Music app to do what you want it to do. You are better off using ducking, where the system just adjusts the relative levels of the two apps for you, allowing the background app (Music) to continue playing but more quietly.

In Swift, how do I let other apps continue to play audio when my app is open?

Set your AVAudioSession category to Ambient.

import AVFoundation.AVAudioSession

AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, error: nil)

This category is also appropriate for “play along” style apps, such as a virtual piano that a user plays while the Music app is playing. When you use this category, audio from other apps mixes with your audio. Your audio is silenced by screen locking and by the Silent switch (called the Ring/Silent switch on iPhone).

Playing sounds in Xcode while playing music from apps

Add try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient) to your code.

AVAudioSessionCategoryAmbient "plays sounds that add polish or interest but are not essential to the app’s use."

Pausing users background music

There is a class MPMusic​Player​Controller which handles Music app functions.It has some class methods like system​Music​Player() that may help in achieving it. But the problem is with any third party app. Suppose music is being played via any third party app. So in this case you won't be able to get access to its method and in case of any interrupt ,like you said, that will be paused.



Related Topics



Leave a reply



Submit