如何使用Combine开发AVPlayerItem观察者?寻求通用实现方案
通用Combine风格的AVPlayerItem观察者实现
一、基于KVO的通用Publisher扩展
利用Combine原生的KVO支持,为AVPlayerItem封装通用的属性监听Publisher,无需手动维护PassthroughSubject的生命周期:
import Combine import AVFoundation extension AVPlayerItem { // 快捷监听播放状态 var statusPublisher: AnyPublisher<AVPlayerItem.Status, Never> { publisher(for: \.status, options: [.initial, .new]) .eraseToAnyPublisher() } // 通用属性监听方法,支持任意KVO兼容的属性 func publisher<Value>(for keyPath: KeyPath<AVPlayerItem, Value>) -> AnyPublisher<Value, Never> { publisher(for: keyPath, options: [.initial, .new]) .map { $0 } .eraseToAnyPublisher() } // 监听播放进度(结合播放状态自动启停定时更新) var playbackProgressPublisher: AnyPublisher<CMTime, Never> { let statusTrigger = publisher(for: \.timeControlStatus) let periodicUpdates = Timer.publish(every: 0.1, on: .main, in: .common) .autoconnect() .map { [weak self] _ in self?.currentTime() ?? .zero } return statusTrigger .flatMap { [weak self] status -> AnyPublisher<CMTime, Never> in guard let self = self else { return Just(.zero).eraseToAnyPublisher() } switch status { case .playing: return periodicUpdates.eraseToAnyPublisher() default: return Just(self.currentTime()).eraseToAnyPublisher() } } .eraseToAnyPublisher() } }
二、AVPlayerItem通知的通用封装
针对AVPlayerItem发送的系统通知,封装成绑定当前item的专属Publisher:
extension AVPlayerItem { // 通用通知监听方法 func notificationPublisher(for name: Notification.Name) -> AnyPublisher<Notification, Never> { NotificationCenter.default.publisher(for: name, object: self) .eraseToAnyPublisher() } // 快捷监听播放结束 var didPlayToEndTimePublisher: AnyPublisher<Notification, Never> { notificationPublisher(for: .AVPlayerItemDidPlayToEndTime) } // 快捷监听加载进度变化 var loadedTimeRangesChangedPublisher: AnyPublisher<Notification, Never> { notificationPublisher(for: .AVPlayerItemNewLoadedTimeRanges) } }
三、使用示例
SwiftUI场景
import SwiftUI struct PlayerStatusView: View { let playerItem: AVPlayerItem @State private var status: AVPlayerItem.Status = .unknown @State private var currentProgress: Double = 0 private var cancellables = Set<AnyCancellable>() var body: some View { VStack(spacing: 16) { Text("播放状态:\(status.displayText)") Text("当前进度:\(String(format: "%.1f", currentProgress))秒") } .onAppear { // 绑定播放状态 playerItem.statusPublisher .assign(to: \.status, on: self) .store(in: &cancellables) // 监听播放进度 playerItem.playbackProgressPublisher .map { $0.seconds } .assign(to: \.currentProgress, on: self) .store(in: &cancellables) // 监听播放结束 playerItem.didPlayToEndTimePublisher .sink { _ in print("播放完成,可执行重播/切换逻辑") } .store(in: &cancellables) } } } extension AVPlayerItem.Status { var displayText: String { switch self { case .unknown: return "加载中" case .readyToPlay: return "就绪" case .failed: return "加载失败" @unknown default: return "未知状态" } } }
UIKit场景
import UIKit class PlayerViewController: UIViewController { let playerItem: AVPlayerItem private var cancellables = Set<AnyCancellable>() init(playerItem: AVPlayerItem) { self.playerItem = playerItem super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() // 监听加载进度变化 playerItem.loadedTimeRangesChangedPublisher .sink { [weak self] _ in guard let self = self else { return } let loadedDuration = self.playerItem.loadedTimeRanges.reduce(CMTime.zero) { result, value in CMTimeAdd(result, value.timeRangeValue.duration) } print("已加载时长:\(loadedDuration.seconds)秒") } .store(in: &cancellables) } }
四、方案优势
- 完全基于Combine原生API,无需手动管理Subject的创建、发送和销毁
- 通用方法支持快速扩展到任意KVO属性或系统通知,复用性强
- 自动处理内存泄漏(通过
weak self和Combine的内存管理机制) - 播放进度监听结合播放状态自动启停,避免无效的定时更新
内容的提问来源于stack exchange,提问作者Rozi
相关产品推荐
相关产品推荐

