【问题标题】:SwiftUI Combine for .onReceive notifications with AVPlayerSwiftUI 结合 .onReceive 通知与 AVPlayer
【发布时间】:2020-03-12 02:36:53
【问题描述】:

我在 SwiftUI 中工作,并且有一个 AudioPlayer 类型,它是 AVPlayer 的子类;它发布 AVPlayer 的 timeControllerStatus(?)(.playing、.paused 和其他?)。我不想继承 AVPlayer,而是想传入一个 AVPlayer 并让它在某些视图中使用 .onReceive 通知我。这是我目前拥有的功能类型:

import AVKit
import Combine

class AudioPlayer: AVPlayer, ObservableObject {
    @Published var buffering: Bool = false

    override init() {
        super.init()
        registerObservers()
    }

    private func registerObservers() {
        self.addObserver(self, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

        if keyPath == "timeControlStatus", let change = change, let newValue = change[NSKeyValueChangeKey.newKey] as? Int, let oldValue = change[NSKeyValueChangeKey.oldKey] as? Int {
            let oldStatus = AVPlayer.TimeControlStatus(rawValue: oldValue)
            let newStatus = AVPlayer.TimeControlStatus(rawValue: newValue)
            if newStatus != oldStatus {
                DispatchQueue.main.async {[weak self] in
                    if newStatus == .playing || newStatus == .paused {
                        self?.buffering = false
                    } else {
                        self?.buffering = true
                    }
                }
            }
        }
    }
}

下面是一个我想要的类的示例(取自Chris Mash's tutorial on SwiftUI & AVPlayer):

import Combine
import AVFoundation

class PlayerItemObserver {
    let publisher = PassthroughSubject<Bool, Never>()
    private var itemObservation: NSKeyValueObservation?

    init(player: AVPlayer) {
        // Observe the current item changing
        itemObservation = player.observe(\.currentItem) { [weak self] player, change in
            guard let self = self else { return }
            // Publish whether the player has an item or not
            self.publisher.send(player.currentItem != nil)
        }
    }

    deinit {
        if let observer = itemObservation {
            observer.invalidate()
        }
    }
}

非常感谢您的帮助。

【问题讨论】:

  • 你想像PlayerItemObserver在播放器中观察currentItem一样观察timeControlStatus吗?

标签: swift notifications swiftui avplayer combine


【解决方案1】:

据我了解,您需要像文章示例中一样观察timeControlStatus。为此,您可以替换观察者:

import Combine
import AVFoundation

class PlayerItemObserver {

    let controlStatusChanged = PassthroughSubject<AVPlayer.TimeControlStatus, Never>()
    private var itemObservation: NSKeyValueObservation?

    init(player: AVPlayer) {

        itemObservation = player.observe(\.timeControlStatus) { [weak self] player, change in
            guard let self = self else { return }
            self.controlStatusChanged.send(player.timeControlStatus)
        }

    }

    deinit {
        if let observer = itemObservation {
            observer.invalidate()
        }
    }
}

// MARK: init view
let player = AudioPlayer()
let playerObserver = PlayerItemObserver(player: player)
let contentView = SongListView(playerObserver: playerObserver)

// MARK: react on changing in view:
struct ContentView: View {

    let playerObserver: PlayerItemObserver

    var body: some View {
        Text("Any view")
            .onReceive(playerObserver.controlStatusChanged) { newStatus in
                switch newStatus {
                case .waitingToPlayAtSpecifiedRate:
                    print("waiting")
                case .paused:
                    print("paused")
                case .playing:
                    print("playing")
                }
        }
    }

}

更新您可以在没有“老派”observe 的情况下使用@PublishedAnyCancellable 来实现相同的目标。最后一个甚至don't need extra code in deinit。这是这个解决方案:

import Combine
import AVFoundation

class PlayerItemObserver {

    @Published var currentStatus: AVPlayer.TimeControlStatus?
    private var itemObservation: AnyCancellable?

    init(player: AVPlayer) {

        itemObservation = player.publisher(for: \.timeControlStatus).sink { newStatus in
            self.currentStatus = newStatus
        }

    }

}

// MARK: you need to change view with new observation, but in general it will be the same
struct ContentView: View {

    let playerObserver: PlayerItemObserver

    var body: some View {
        Text("Any view")
            .onReceive(playerObserver.$currentStatus) { newStatus in
                switch newStatus {
                case nil:
                    print("nothing is here")
                case .waitingToPlayAtSpecifiedRate:
                    print("waiting")
                case .paused:
                    print("paused")
                case .playing:
                    print("playing")
                }
        }
    }

}

【讨论】:

  • 我使用了您的第二个(更新)选项,效果很好!我喜欢它的干净程度。
【解决方案2】:

NSObject 有一个方法可以为任何符合 KVO 的属性提供Publisher。它没有记录在案,但在 WWDC 2019 会议中进行了讨论。例如,Raleigh Ledet 描述了它starting at 25m36s in Session 231: Introducing SwiftUI,Michael LeHew 使用了它at 11m47s in Session 721: Combine in Practice

方法是declared like this:

public func publisher<Value>(
    for keyPath: KeyPath<Self, Value>,
    options:NSKeyValueObservingOptions = [.initial, .new]
) -> NSObject.KeyValueObservingPublisher<Self, Value>

因此,例如,您可以这样使用它:

player.publisher(for: \.timeControlStatus, options: [.initial])
    .sink { print("player status: \($0)") }
    .store(in: &tickets)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-14
    • 1970-01-01
    • 1970-01-01
    • 2020-09-15
    • 2022-11-17
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多