SwiftUI 将 .onReceive 通知与 AVPlayer 相结合
SwiftUI Combine for .onReceive notifications with AVPlayer
我在 SwiftUI 中工作,并且有一个 AudioPlayer 类型,它是 AVPlayer 的子class;它发布 AVPlayer 的 timeControllerStatus(?)(.playing、.paused 和其他?)。而不是 subclassing 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
}
}
}
}
}
}
这里有一个 class 的例子,就像我想要的那样(取自 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")
}
}
}
}
UPDATE 您可以在没有 "old school" observe
的情况下使用 @Published
和 AnyCancellable
实现相同的效果。最后一个甚至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")
}
}
}
}
NSObject
有一种方法可以为任何 KVO-compliant 属性 提供 Publisher
。它没有记录在案,但在 WWDC 2019 会议上进行了讨论。例如,Raleigh Ledet 对其进行了描述 starting at 25m36s in Session 231: Introducing SwiftUI, and Michael LeHew used it at 11m47s in Session 721: Combine in Practice.
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: \([=11=])") }
.store(in: &tickets)
我在 SwiftUI 中工作,并且有一个 AudioPlayer 类型,它是 AVPlayer 的子class;它发布 AVPlayer 的 timeControllerStatus(?)(.playing、.paused 和其他?)。而不是 subclassing 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
}
}
}
}
}
}
这里有一个 class 的例子,就像我想要的那样(取自 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")
}
}
}
}
UPDATE 您可以在没有 "old school" observe
的情况下使用 @Published
和 AnyCancellable
实现相同的效果。最后一个甚至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")
}
}
}
}
NSObject
有一种方法可以为任何 KVO-compliant 属性 提供 Publisher
。它没有记录在案,但在 WWDC 2019 会议上进行了讨论。例如,Raleigh Ledet 对其进行了描述 starting at 25m36s in Session 231: Introducing SwiftUI, and Michael LeHew used it at 11m47s in Session 721: Combine in Practice.
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: \([=11=])") }
.store(in: &tickets)