SwiftUI 结合 .onReceive 通知与 AVPlayer

标签 swift notifications swiftui avplayer combine

我在 SwiftUI 中工作,并且有一个 AudioPlayer 类型,它是 AVPlayer 的子类;它发布 AVPlayer 的 timeControllerStatus(?)(.playing、.paused 和其他?)。我不想继承 AVPlayer,而是想传入一个 AVPlayer 并让它在某些 View 中使用 .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()
        }
    }
}

非常感谢您的帮助。

最佳答案

据我了解,您需要像文章示例中一样观察 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")
                }
        }
    }

}

关于SwiftUI 结合 .onReceive 通知与 AVPlayer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60646857/

相关文章:

ios - 如何使用不同大小的 subview 制作 UIStackView ?

swift - 从数组中获取范围时为 "Array index is out of range:"

ios - 无法使用mockURLSession调用非函数类型 'URLSession'错误的值

ios - Firebase FCM主题-可以是已订阅用户的用户ID吗?

ios - ObservableObject 中的 ObservedObject 不刷新 View

swift - Swift 中以可变数组为值的字典执行速度很慢?如何优化或正确构建?

parse-platform - 解析推送通知教程问题 - android studio

objective-c - 关闭推送通知横幅

macos - macOS 上的 SwiftUI ListStyle

ios - 是否可以制作包含SwiftUI的Swift项目模板?