In Swift 5 how would you add Audio Controls to Control Center via background audio?

846 Views Asked by At

I'm playing audio from a JSON source that I built to feed the audio URL and it's audio information to my app (Such as title, description, and cover art URL). The audio is a radio station feed. Audio works, play/stop controls work, cover art works, and background audio works. I'm stumped on adding the controls to Control Center to play/stop the audio while outside the app. I've taken a look at Apple's Documentation on this and it's very straight forward. I've also enabled the build setting to allow for background fetch, background audio, and Bluetooth. But it doesn't seem to work on my iPhone attached to Xcode via USB-C (I'm assuming iOS Simulator doesn't support Control Center). Below is my working code. Any thoughts on how to get this working? Do I need to pass on the audio to setupRemoteTransportControls()? I'm assuming though Title and Cover Art will need to be passed from data source, but overall the Control Center will not recognize that the app is playing audio.

ViewController.swift

import UIKit
import MediaPlayer
import AVFoundation
import Foundation

class ViewController: UIViewController {

    var player: AVPlayer!
    var playerItem: AVPlayerItem!
    
    var audioCheck: Timer?
    var timer: Timer?
    
    var passText: String? = "Test"

    @IBOutlet weak var trackTitle: UILabel!
    @IBOutlet weak var togglePlay: UIButton!
    @IBOutlet weak var coverPhoto: UIImageView!

    func loadAudio() {
        let audioURL = URL.init(string: "AUDIO_URL_GOES_HERE")
        player = AVPlayer.init(url: audioURL!)
    }
    
    let minutes = 60

    func playAudio() {
               let audioURL = URL.init(string: "AUDIO_URL_GOES_HERE")
               player = AVPlayer.init(url: audioURL!)
               player.play()
           }
    
    @IBAction func OpenPlayer(_ sender: Any) {
        performSegue(withIdentifier: "PlayerSegue", sender: self)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        loadAudio()
        
        
        // Audio in the background
        let audioSession = AVAudioSession.sharedInstance()
        
        do {
            try audioSession.setCategory(AVAudioSession.Category.playback)
        } catch {
            print("Error: Audio is not paying in the background.")
        }
        
        
        // Timer checking if other audio sources are running
        audioCheck = Timer.scheduledTimer(timeInterval: 0, target: self, selector: #selector(timedAudioCheck), userInfo: nil, repeats: true)
        struct currentTrack: Codable {
            let title: String
            let artwork_url: String
        }
        struct getTrack: Codable {
            // let name: String
            // let status: String
            let current_track: currentTrack
            
        }

        let jsonURL = URL(string: "JSON_URL_GOES_HERE")!
        URLSession.shared.dataTask(with: jsonURL) {data, _, _ in
            if let data = data {
                let trackData = try? JSONDecoder().decode([getTrack].self, from: data)
                // print(users)
                // print(trackData![0].current_track.title)
                // print(trackData![0].current_track.artwork_url)
                
                DispatchQueue.main.async {
          
                        self.trackTitle.text = trackData![0].current_track.title
                        let coverPhotoURL = trackData![0].current_track.artwork_url
                        if let coverPhotoConverted = URL(string: coverPhotoURL) {
                            do {
                                let coverPhotoData = try Data(contentsOf: coverPhotoConverted)
                                self.coverPhoto.image = UIImage(data: coverPhotoData)
                            } catch {
                                
                            }
                        }
                }
            }
        }.resume()
        
        _ = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { timer in
            // print("Data updated!")
           let jsonURL = URL(string: "JSON_URL_GOES_HERE")!
           URLSession.shared.dataTask(with: jsonURL) {data, _, _ in
               if let data = data {
                   let trackData = try? JSONDecoder().decode([getTrack].self, from: data)
                   // print(users)
                   // print(trackData![0].current_track.title)
                   // print(trackData![0].current_track.artwork_url)
                   
                   DispatchQueue.main.async {
             
                           self.trackTitle.text = trackData![0].current_track.title
                           let coverPhotoURL = trackData![0].current_track.artwork_url
                           if let coverPhotoConverted = URL(string: coverPhotoURL) {
                               do {
                                   let coverPhotoData = try Data(contentsOf: coverPhotoConverted)
                                   self.coverPhoto.image = UIImage(data: coverPhotoData)
                               } catch {
                                   
                               }
                           }
                   }
               }
           }.resume()
        }
        
        func setupRemoteTransportControls() {
        // Get the shared MPRemoteCommandCenter
        let commandCenter = MPRemoteCommandCenter.shared()

        // Add handler for Play Command
        commandCenter.playCommand.addTarget { [unowned self] event in
            if self.player.rate == 0.0 {
                self.player.play()
                return .success
            }
            return .commandFailed
        }

        // Add handler for Pause Command
        commandCenter.pauseCommand.addTarget { [unowned self] event in
            if self.player.rate == 1.0 {
                self.player.pause()
                return .success
            }
            return .commandFailed
        }
    }
        
        
        // Apple's Documentation on playing audio in control center
        func setupRemoteTransportControls() {
            // Get the shared MPRemoteCommandCenter
            let commandCenter = MPRemoteCommandCenter.shared()
        
            // Add handler for Play Command
            commandCenter.playCommand.addTarget { [unowned self] event in
                if self.player.rate == 0.0 {
                    self.player.play()
                    return .success
                }
                return .commandFailed
            }
        
            // Add handler for Pause Command
            commandCenter.pauseCommand.addTarget { [unowned self] event in
                if self.player.rate == 1.0 {
                    self.player.pause()
                    return .success
                }
                return .commandFailed
            }
        }
        
        func setupNowPlaying() {
            // Define Now Playing Info
            var nowPlayingInfo = [String : Any]()
            nowPlayingInfo[MPMediaItemPropertyTitle] = "My Movie"
        
            if let image = UIImage(named: "lockscreen") {
                nowPlayingInfo[MPMediaItemPropertyArtwork] =
                    MPMediaItemArtwork(boundsSize: image.size) { size in
                        return image
                }
            }
            nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playerItem.currentTime().seconds
            nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = playerItem.asset.duration.seconds
            nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
        
            // Set the metadata
            MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
        }
        

    }

    // Along with timer, checks if other audio sources are playing and resets the audio in the app
    @objc func timedAudioCheck() {
        if (AVAudioSession.sharedInstance().secondaryAudioShouldBeSilencedHint) {
            togglePlay.setTitle("Play", for: .normal)
        } else if (player.rate == 0) {
            togglePlay.setTitle("Play", for: .normal)
        } else {
            togglePlay.setTitle("Puase", for: .normal)
        }
    }
    
    @IBAction func togglePlay(_ sender: UIButton) {

        if player.rate == 0 {
            // Plays the audio stream
            sender.setTitle("Pause", for: .normal)
            playAudio()
        } else {
            sender.setTitle("Play", for: .normal)
            player.pause()
        }
    }
}

Apple's suggestion to add Control Center Support

func setupRemoteTransportControls() {
    // Get the shared MPRemoteCommandCenter
    let commandCenter = MPRemoteCommandCenter.shared()

    // Add handler for Play Command
    commandCenter.playCommand.addTarget { [unowned self] event in
        if self.player.rate == 0.0 {
            self.player.play()
            return .success
        }
        return .commandFailed
    }

    // Add handler for Pause Command
    commandCenter.pauseCommand.addTarget { [unowned self] event in
        if self.player.rate == 1.0 {
            self.player.pause()
            return .success
        }
        return .commandFailed
    }
}

func setupNowPlaying() {
    // Define Now Playing Info
    var nowPlayingInfo = [String : Any]()
    nowPlayingInfo[MPMediaItemPropertyTitle] = "My Movie"

    if let image = UIImage(named: "lockscreen") {
        nowPlayingInfo[MPMediaItemPropertyArtwork] =
            MPMediaItemArtwork(boundsSize: image.size) { size in
                return image
        }
    }
    nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playerItem.currentTime().seconds
    nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = playerItem.asset.duration.seconds
    nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate

    // Set the metadata
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
0

There are 0 best solutions below