I have attached a fully working SwiftUI application below. For example, when I am playing Apple Music in the background and I go to the app and press the play button, I am noticing about 700 to 800ms of hang every single time. Received no answer from Apple regarding this.
And I want the app to take over the music from other apps. I do NOT want the app to have its audio mixed with others.
struct ContentView: View {
@EnvironmentObject var contentViewModel: ContentViewModel
var body: some View {
Button {
AudioManager.shared.play()
} label: {
Image(systemName: contentViewModel.isPlaying ? "pause.fill" : "play.fill")
.font(.title)
.foregroundStyle(.white)
}
}
}
class ContentViewModel: ObservableObject {
@Published var isPlaying = false
}
class AudioManager {
static let shared = AudioManager()
private let audioEngine = AVAudioEngine()
private let playerNode = AVAudioPlayerNode()
let contentViewModel = ContentViewModel()
init() {
startAudioSession()
setupAudioEngine()
setupNotifications()
}
func startAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback)
try session.setActive(true)
} catch {
print("Failed to start Audio Session: (error.localizedDescription)")
}
}
func setupAudioEngine() {
///Pass in a music file to the url here
let url = Bundle.main.url(forResource: "Your song goes here", withExtension: "mp3")!
do {
let audioFile = try AVAudioFile(forReading: url)
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: audioFile.processingFormat)
playerNode.scheduleFile(audioFile, at: nil, completionCallbackType: .dataPlayedBack) {_ in
print("Song finished playing")
}
} catch let error {
print(error.localizedDescription)
}
}
func play() {
if playerNode.isPlaying {
playerNode.pause()
audioEngine.pause()
contentViewModel.isPlaying = false
} else {
do {
try audioEngine.start()
playerNode.play()
contentViewModel.isPlaying = true
} catch {
print(error.localizedDescription)
}
}
}
//Handling interruptions
func setupNotifications() {
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: AVAudioSession.sharedInstance())
}
@objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
// Switch over the interruption type.
switch type {
case .began:
// An interruption began. Update the UI as necessary.
print("Interruption Began")
play()
case .ended:
// An interruption ended. Resume playback, if appropriate.
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// An interruption ended. Resume playback.
play()
} else {
// An interruption ended. Don't resume playback.
}
default: ()
}
}
}