Ismailp
Ismailp

Reputation: 2383

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

Upvotes: 6

Views: 3446

Answers (2)

Fred Klein
Fred Klein

Reputation: 658

As explained at https://developer.apple.com/documentation/combine/performing-key-value-observing-with-combine you can create a combine listener

statusObserver = player.publisher(for: \.timeControlStatus)
    .receive(on: DispatchQueue.main)
    .sink { newStatus in
        isPlaying = newStatus == .playing
    }

where isPlaying: Bool and statusObserver: AnyCancellable? are private @State properties on my view.

Upvotes: 0

Asperi
Asperi

Reputation: 257749

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

Upvotes: 3

Related Questions