AVFoundation player.publisher().assign() for @Observable/@ObservationTracked?

Following this Apple Article, I copied their code over for observePlayingState().

The only difference I am using @Observable instead of ObservableObject and @Published for var isPlaying.

We get a bit more insight after removing the $ symbol, leading to a more telling error of: Cannot convert value of type 'Bool' to expected argument type 'Published<Bool>.Publisher'

Is there anyway to get this working with @Observable?

Replies

The article you linked to is based on Combine. The specific error you’re getting is because you’re building a Combine publisher pipeline where the terminal assign(to:) method requires a publisher, and you don’t have a publisher.

It’s not clear why you’re trying to move from Combine to Observation, given that you have existing Combine code that works. However, if you do want to make that change then you need to look at it holistically. What is this code trying to do? And what’s the best way to accomplish that with Observation? The answer to that first question is:

  • publisher(for:) uses KVO to observe the timeControlStatus property.

  • .receive(on:) ensures that any changes are received on the main thread.

  • .map(_:) converts the stream of state values to a stream of Booleans, based on whether the state is .playing.

  • assign(to:) assigns new values to the isPlaying property.

You have a couple of approaches for switching to Observation:

  • Do all of this using Swift concurrency primitives.

  • Make a minimal change.

The latter will be easier, obviously (-: To do that, change the assign(to:) to a sink(receiveValue:) where the closure sets self.isPlaying.

IMPORTANT sink(receiveValue:) returns an AnyCancellable. You must retain that to keep you Combine pipeline active.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Quinn, thanks for the insight! The reason for deviating from the approach in the link was to explore some of the new features with @Observable and converting projects which previously used ObservableObjects. It seems that most classes can be converted quite easily, but this is a case where it takes a deeper understanding to adjust to the new approach.

I am new to all this (AVPlayer, Observable, Concurrency), but the solution I ended up implementing to represent Play state is to just look at the rate and return a bool, since AVPlayer just adjust the rate from 0.0 / (> 0.0) to Pause/Play.

            .receive(on: DispatchQueue.main)
            .sink { [weak self] rate in
                Logger.funcUpdate("Rate changed to: \(rate)")
                if rate == 0 {
                    self?.isPlaying = false
                } else {
                    self?.isPlaying = true
                }
            }
            .store(in: &subscriptions)