Search code examples
iosswiftswiftuiavplayer

Listen to AVPlayer timeControlStatus changes in SwiftUI


So trying to implement as simple observer for AVPlayer in SwiftUI. I've added the observer which is triggered but haven't figured out how/where to implement observeValue as per the Apple documentation: https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/responding_to_playback_state_changes

So far I have my AVPlayer class as follows:

class Player: AVPlayer, ObservableObject {

    @Published var isPlaying: Bool = false

    static var shared = AVPlayer()
    static var episodeId: Int?

    static func playItem(at itemURL: String, episodeId: Int) {
        let url = URL(string: itemURL)
        Player.shared = AVPlayer(url: url!)
        Player.episodeId = episodeId
        Player.shared.addObserver(self.shared, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil)
    }
}

So the question is where I should implement this for it to work:

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
    if Player.shared.timeControlStatus == .playing {
        // Set @Published isPlaying to true
    }
}

Solution

  • It can like the following (don't use static it's not needed in this case, anyway you'll be needed instance to use in @ObservedObject)

    Of course it is not final Player, but the direction to evolve it should be clear:

    class Player: AVPlayer, ObservableObject {
    
        @Published var isPlaying: Bool = false
    
        private var playerContext = 0
    
        var player: AVPlayer? = nil
        var episodeId: Int?
    
        func playItem(at itemURL: String, episodeId: Int) {
            guard let url = URL(string: itemURL) else { return }
    
            // cleanup for previous player
            self.player?.removeObserver(self, forKeyPath: "timeControlStatus")
    
            // setup new player
            let newPlayer = AVPlayer(url: url)
            newPlayer.addObserver(self, forKeyPath: "timeControlStatus", options: [.old, .new], context: &playerContext)
    
            self.player = newPlayer
            self.episodeId = episodeId
        }
    
        override func observeValue(forKeyPath keyPath: String?,
                                   of object: Any?,
                                   change: [NSKeyValueChangeKey : Any]?,
                                   context: UnsafeMutableRawPointer?) {
    
            guard context == &playerContext else { // give super to handle own cases
                   super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
                   return
            }
            if self.player?.timeControlStatus == .playing {
                self.isPlaying = true
            }
        }
    }