UNNotificationContentExtension Rich Notification Play/Pause Button Not Changing State

In an iOS UNNotificationContentExtension with a media player, I have an AVPlayer which can either play a WAV or an MP4 remotely depending on the push payload userInfo dictionary.

I have implemented mediaPlayPauseButtonFrame, mediaPlayPauseButtonTintColor, and mediaPlayPauseButtonType, have overridden canBecomeFirstResponder to force true, and set the view to becomeFirstResponder when the AVPlayer is added.

I have implemented the UNNotificationContentExtension protocol's mediaPlay and mediaPause methods. I also have subscribed to the .AVPlayerItemDidPlayToEndTime (NS)Notification and I call a method on the VC when it returns, which calls mediaPause.

When the AVPlayer reaches the end, the .AVPlayerItemDidPlayToEndTime Notification is properly emitted, my method is called, and mediaPause is called. However, the media play/pause button provided by UNNotificationContentExtension remains visibly in the "playing" state instead of changing to the "pause" state. The button correctly changes its display state when the user presses the play/pause button manually, so it works.

And so, collective Obis Wan Kenobi, what am I doing wrong? I have tried resigning first responder, have no access to the button itself -- as far as I know -- and am wondering where to go next.

(This is the only thing not working by the way.)

Sanitized example:

import UIKit
import UserNotifications
import UserNotificationsUI

class NotificationViewController: UIViewController, UNNotificationContentExtension {
    // Constants
    private let viewModel = ...

    private var mediaPlayer: AVPlayer?
    private var mediaPlayerLayer: AVPlayerLayer?
    private var mediaPlayerItem: AVPlayerItem? {
        mediaPlayer?.currentItem
    }

    override var canBecomeFirstResponder: Bool {
        true
    }
    
    // MARK: - UNNotificationContentExtension var overrides
    
    var mediaPlayPauseButtonType: UNNotificationContentExtensionMediaPlayPauseButtonType {
        return .default
    }
    
    var mediaPlayPauseButtonFrame: CGRect {
        return CGRect(x: 0.0, y: 0.0, width: 50.0, height: 50.0)
    }
    
    var mediaPlayPauseButtonTintColor: UIColor {
        return .blue
    }
    
    ...
    
    func didReceive(_ notification: UNNotification) {
        ...
        // Process userInfo for url
    }
    
    ...
    
    @MainActor
    func playAudio(from: URL) async {
        let mediaPlayer = AVPlayer(url: url)
        let mediaPlayerLayer = AVPlayerLayer(player: audioPlayer)
        
        ...
        // view setup
        
        mediaPlayerLayer.frame = ...
        
        self.mediaPlayer = mediaPlayer
        self.mediaPlayerLayer = mediaPlayerLayer
        
        self.view.layer.addSublayer(mediaPlayerLayer)
        
        becomeFirstResponder()
    }
    
    // MARK: - UNNotificationContentExtension
    
    func mediaPlay() {
        mediaPlayer?.play()
    }
    
    func mediaPause() {
        mediaPlayer?.pause()
    }
    
    // MARK: - Utilities
    
    private func subscribe(to item: AVPlayerItem) {
        NotificationCenter.default.addObserver(self, selector: #selector(playedToEnd),
                                               name: .AVPlayerItemDidPlayToEndTime, object: item)
    }
    
    @objc 
    func playedToEnd(notification: NSNotification) {
        mediaPause()
    }
}
  • I forgot to show the import AVKit statement at the top somehow

Add a Comment

Replies

I've tried a bunch more things but no luck, should I assume this is a bug? Undocumented limitation?