I'm building a Swift 5 music app where a table view loads and lists multiple objects from a JSON API. After clicking on an entry, the navigator calls the detail view for the selected item.
Below, you can see my detail view controller, called BroadcastDetailViewController
. As you can see there is a playBroadcastButton
which'll play the audio file and setup the AVAudioSession
.
So far so good. But after I've implemented the possibility to scrub through the audio file via control center scrubber (commandCenter.changePlaybackPositionCommand.addTarget
) and go back to my table view controller and select another item to play it's audio file, the AVPlayer layers multiple audio files on each other and plays them simultaneously. This is a behavior I don't want. Before I implemented the control buttons, the whole audio file replacement process worked fine.
My question is, why does the player suddenly play another file on top of the other, instead of muting/ducking the old one and replace it with the new one?
class BroadcastDetailViewController: UIViewController {
var player = AVPlayer()
var playerItem: AVPlayerItem!
var broadcast:Broadcasts?
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBAction func playBroadcastButton(_ sender: Any) {
player.play()
setupAVAudioSession()
}
private func setupAVAudioSession() {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
debugPrint("AVAudioSession is Active and Category Playback is set")
UIApplication.shared.beginReceivingRemoteControlEvents()
setupCommandCenter()
} catch {
debugPrint("Error: \(error)")
}
}
private func setupCommandCenter() {
// Meta
var nowPlayingInfo = [String : Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = broadcast?.title ?? "Radio Bass"
nowPlayingInfo[MPMediaItemPropertyArtist] = "Radio Bass"
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = "Radio Bass."
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = playerItem.currentTime().seconds
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = playerItem.asset.duration.seconds
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.pauseCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.play()
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.player.pause()
return .success
}
// Scrubber
commandCenter.changePlaybackPositionCommand.addTarget { event in
let seconds = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime ?? 0
let time = CMTime(seconds: seconds, preferredTimescale: 1)
self.player.seek(to: time)
return .success
}
}
override func viewDidLoad() {
super.viewDidLoad()
let urlAudioString = broadcast?.audio
playerItem = AVPlayerItem(url: URL(string: urlAudioString!)!)
player = AVPlayer(playerItem: playerItem)
title = broadcast?.title
let urlImageString = broadcast?.image
let urlImage = URL(string: urlImageString!)
titleLabel.text = broadcast?.title
imageView.load(url: urlImage!)
}
}
Release/free and stop AVPlayer
and AVPlayerItem
before going back to your table view controller
avPlayer.pause()
avPlayer.cancelPendingPrerolls() // stops network requests
avPlayer.replaceCurrentItem(with: nil)