Play/Pause and Elapsed Time not updating in iOS command center properly
Asked Answered
A

1

15

I have a video player that can play from the iOS command center and lock screen. When I toggle a play/pause button in my app, it should update the play/pause button in the command center (MPRemoteCommandCenter) by updating the nowPlayingInfo (MPNowPlayingInfoCenter). I'm not sure why it's not updating.

For example, if I pause the video with a custom button in my app, the command center still shows the pause button (meaning the video is still playing which is wrong.)

enter image description here

This is how I update the nowPlayingInfo:

func updateMPNowPlayingInforCenterMetadata() {
    guard video != nil else {
        nowPlayingInfoCenter.nowPlayingInfo = nil
        return
    }

    var nowPlayingInfo = nowPlayingInfoCenter.nowPlayingInfo ?? [String: Any]()

    let image: UIImage
    if let placeholderLocalURL = video.placeholderLocalURL, let placeholderImage = UIImage(contentsOfFile: placeholderLocalURL.path) {
        image = placeholderImage
    } else {
        image = UIImage()
    }

    let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { _ -> UIImage in
        return image
    })

    nowPlayingInfo[MPMediaItemPropertyTitle] = video.title
    nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = video.creator?.name ?? " "
    nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork

    nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Float(video.duration)
    nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Float(currentTime) // CMTimeGetSeconds(player.currentItem!.currentTime())
    nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate
    nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = player.rate

    nowPlayingInfoCenter.nowPlayingInfo = nowPlayingInfo

    if player.rate == 0.0 {
        state = .paused
    } else {
        state = .playing
    }
}

With KVO, when the player's rate changes, I call this function:

// MARK: - Key-Value Observing Method

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &assetPlaybackManagerKVOContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    } else if keyPath == #keyPath(AVPlayer.rate) {
        updateMPNowPlayingInforCenterMetadata()
    }
}

Any thoughts?

UPDATE

I found a solution though but not perfect in my case. So in my app I have 2 view controller's. Let's call them FeedVC and PlayerVC. So FeedVC has AVPlayer's that are always playing but are muted. If you click on one of them, then the PlayerVC is created and plays the full video. If I pause the AVPlayer's in FeedVC before going to PlayerVC then the "play/pause" button in the NowPlayingInfoCenter works perfectly!

Is there a way to make this work without having to pause the videos in the FeedVC?

Another issue is that the elapsed time keeps counting if I don't pause the players in the FeedVC. It seems that if multiple players are playing, the play/pause button and elapsed time are incorrect.

Asterism answered 7/4, 2017 at 15:54 Comment(2)
I got you man just give me time to get home and I'll show you my solution.Hoagland
iam also facing same problem..did you found solution for thisGynaeco
H
12

When you setting the dictionary for nowPlayingInfo you will want to set the MPNowPlayingInfoPropertyPlaybackRate value appropriately. The MPNowPlayingInfoCenter is expecting either a 1.0 (playing) or 0.0 (not playing) value as a Double wrapped in a NSNumber object. Below you will find the code for how I'm setting the nowPlayingInfo in my project.

func setNowPlayingMediaWith(song: SUSong, currentTime: Double?) {

    var playingInfo:[String: Any] = [:]

    if let title = song.title {
        playingInfo[MPMediaItemPropertyTitle] = title
    }

    if let songID = song.id {
        playingInfo[MPMediaItemPropertyPersistentID] = songID
    }
    if let artist = song.artist, let artistName = artist.name {
        playingInfo[MPMediaItemPropertyArtist] = artistName
    }

    if let album = song.album, let albumTitle = album.title {
        var artwork:MPMediaItemArtwork? = nil
        if let album = song.album, let artworkData = album.artwork {
            artwork = MPMediaItemArtwork(boundsSize: Constants.Library.Albums.thumbSize, requestHandler: { (size) -> UIImage in
                return UIImage(data: artworkData as Data)!
            })
        }
        if artwork != nil {
            playingInfo[MPMediaItemPropertyArtwork] = artwork!
        }
        playingInfo[MPMediaItemPropertyAlbumTitle] = albumTitle
    }

    var rate:Double = 0.0
    if let time = currentTime {
        playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = time
        playingInfo[MPMediaItemPropertyPlaybackDuration] = song.duration
        rate = 1.0
    }
    playingInfo[MPNowPlayingInfoPropertyPlaybackRate] = NSNumber(value: rate)
    playingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.audio.rawValue)
    MPNowPlayingInfoCenter.default().nowPlayingInfo = playingInfo
}

In this method I am passing the song that my player is has currently loaded. Whenever the user chooses to play or pause I call setNowPlayingMediaWith(song:currentTime:) to update the device's control console.

I keep track of the currentTime (Double) as a property of my player. If there is a currentTime passed in that means we are meant to be playing, so set the MPNowPlayingInfoPropertyPlaybackRate to 1.0 and set the MPNowPlayingInfoPropertyElapsedPlaybackTime to currentTime. This will set the current time to start automatically playing in the device's control console.

Likewise, if there is no currentTime passed then we have stopped or paused. In this case we set the MPNowPlayingInfoPropertyPlaybackRate to 0.0 and we do not include the MPNowPlayingInfoPropertyElapsedPlaybackTime.

Download my app to see this in action.


EDIT (answer to comments)

"Is there a way to make this work without having to pause the videos in the FeedVC"

Without seeing your code it is difficult to give you a definite answer. It would make sense though to pause any ongoing media before starting your PlayerVC's media.


"the elapsed time keeps counting"

The elapsed time will countdown the elapsed time based on an NSTimer property on NowPlayingInfoCenter. This timer will stop only when the timers value has reached the value you set in MPMediaItemPropertyPlaybackDuration, or when you update the MPNowPlayingInfoPropertyElapsedPlaybackTime.

My suggestion is to write a method that "clears out" the NowPlayingInfoCenter. This method should set the will set all of key values to either 0.0 or nil respectively. Call this "clear out" method each time before you play your media in PlayerVC. Then once you play from PlayerVC, set the NowPlayingInfoCenter key values like you would in the method I pasted in my answer to set the new values for the new playing media.

Hoagland answered 10/4, 2017 at 23:32 Comment(11)
Doesn't my code automatically do this with nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = player.rate and nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = player.rate?Asterism
Also, I got that from Apple's sample code: developer.apple.com/library/content/samplecode/…Asterism
I will update my answer later tonight. I'm at work and don't have my personal computer on me. But I have done this in a personal project and it works great. I thought it went like the above but I will update this tonight.Hoagland
Ok sure, thank you! Please also take a look at my "Update" because I also mention that elapsed time seems to not work properly.Asterism
@Asterism I updated my answer. Please let me know if this helps you out. If not we can get on a chat and I can walk you through it.Hoagland
I get the current time of the player with CMTimeGetSeconds(player.currentTime()), but this does not return nil. Therefore with your function, when do you decide to pass in nil or not to the parameter currentTime: Double? ? Basically, with CMTimeGetSeconds(player.currentTime()) that means I never pass in nilAsterism
This doesn't work for me, I tried setting the currentTime to nil when I pause the video just for testing, but the play/pause button doesn't change correctly. I still think it's due to the other AVPlayer's playing in a different view controllerAsterism
Keep in mind it isn't a 100% copy and paste kind of thing. This is just what I've done in my project. You won't have an SUSong for example. Can you paste your code so I can see what you are doing?Hoagland
Yep I get that its not copy/paste : ) Quick question, did you run a test where an AVPlayer is always playing in a different view controller -- similar to what I said in the Update of my question?Asterism
Did you see my previous comment and question above? ^^Asterism
@Asterism The same issue is happening to me with an instance of AVPlayer and an instance of AVPlayerViewController. Luckily, there's an option on AVPlayerViewController to set updatesNowPlayingInfoCenter to falseGlasshouse

© 2022 - 2024 — McMap. All rights reserved.