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()
}
}