tvOS中AVPlayerViewController的PlayPause按钮手势异常问题
在AVPlayerViewController中捕获用户播放/暂停操作的解决方案
问题根源
AVPlayerViewController自带一套播放控制UI,它内部的播放暂停按钮会优先拦截点击事件:点击暂停时,内部按钮先处理了操作,你的自定义手势根本收不到信号;而点击播放时,内部逻辑的事件传递优先级不同,手势才能正常触发。另外,一旦调用UIApplication.shared.beginReceivingRemoteControlEvents(),系统会全盘接管媒体控制事件,你的自定义手势就彻底失效了。
常规合规解决方案(无私有API/方法交换)
方案1:监听AVPlayer状态变化并区分操作类型
通过KVO监听播放器的timeControlStatus,结合reasonForWaitingToPlay判断是否为用户主动触发的操作:
override func viewDidLoad() { super.viewDidLoad() // 注册KVO监听播放器状态 avPlayer.addObserver(self, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "timeControlStatus", let player = object as? AVPlayer else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } switch player.timeControlStatus { case .paused: // 若reasonForWaitingToPlay为nil,说明不是缓冲等被动暂停,大概率是用户主动操作 if player.reasonForWaitingToPlay == nil { print("用户主动触发暂停") // 执行你的暂停业务逻辑 } case .playing: print("用户主动触发播放") // 执行你的播放业务逻辑 default: break } } deinit { // 记得移除KVO监听 avPlayer.removeObserver(self, forKeyPath: "timeControlStatus") }
方案2:自定义播放控制栏(完全可控)
关闭AVPlayerViewController的默认控制栏,自己实现播放暂停按钮,彻底掌控交互逻辑:
override func viewDidLoad() { super.viewDidLoad() // 隐藏系统默认控制栏 showsPlaybackControls = false // 创建自定义播放暂停按钮 let playPauseBtn = UIButton(type: .system) playPauseBtn.setTitle("暂停", for: .normal) playPauseBtn.frame = CGRect(x: view.center.x - 50, y: view.frame.maxY - 100, width: 100, height: 50) playPauseBtn.addTarget(self, action: #selector(customPlayPauseTapped), for: .touchUpInside) // 添加到contentOverlayView(AVPlayerViewController专门用于叠加自定义视图的容器) contentOverlayView?.addSubview(playPauseBtn) avPlayer.play() } @objc private func customPlayPauseTapped(_ sender: UIButton) { switch avPlayer.timeControlStatus { case .playing: avPlayer.pause() sender.setTitle("播放", for: .normal) print("用户点击自定义按钮暂停") case .paused: avPlayer.play() sender.setTitle("暂停", for: .normal) print("用户点击自定义按钮播放") default: break } }
方案3:调整自定义手势的挂载位置与优先级
把手势添加到contentOverlayView而非根view,并设置手势代理允许和系统手势同时识别:
class YourPlayerVC: AVPlayerViewController, UIGestureRecognizerDelegate { lazy var playPauseButtonTap: UITapGestureRecognizer = { let gesture = UITapGestureRecognizer(target: self, action: #selector(playPauseTapped)) gesture.allowedPressTypes = [NSNumber(value: UIPress.PressType.playPause.rawValue)] gesture.delegate = self return gesture }() override func viewDidLoad() { super.viewDidLoad() // 将手势添加到contentOverlayView contentOverlayView?.addGestureRecognizer(playPauseButtonTap) } // 允许自定义手势和系统内置手势同时触发 func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @objc private func playPauseTapped() { print("HELLO PLAY PAUSE TAPPED") togglePlayPause() } private func togglePlayPause() { switch avPlayer.timeControlStatus { case .playing: avPlayer.pause() case .paused: avPlayer.play() default: break } } }
非常规解决方案(依赖私有视图层级,不推荐)
遍历AVPlayerViewController的子视图,找到内置的播放暂停按钮并添加自定义响应逻辑:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 递归遍历子视图查找播放暂停按钮 findPlayPauseButton(in: view) } private func findPlayPauseButton(in view: UIView) { for subview in view.subviews { if let control = subview as? UIControl { // 通过按钮标题判断是否为播放暂停按钮(仅作示例,私有视图结构可能随系统版本变化) let title = control.title(for: .normal) if title == "暂停" || title == "播放" { control.addTarget(self, action: #selector(internalPlayPauseBtnTapped), for: .touchUpInside) return } } findPlayPauseButton(in: subview) } } @objc private func internalPlayPauseBtnTapped(_ sender: UIControl) { print("内置播放暂停按钮被点击") if avPlayer.timeControlStatus == .playing { print("用户点击了暂停") } else { print("用户点击了播放") } }
注意:此方案依赖苹果未公开的视图结构,iOS版本更新后可能失效,且存在被App Store拒绝的风险。
关于beginReceivingRemoteControlEvents的处理
调用该方法后,系统会接管所有媒体控制事件(包括屏幕按钮、耳机线控等),自定义手势会失效。若必须使用远程控制,可通过监听系统通知同步业务逻辑:
override func viewDidLoad() { super.viewDidLoad() UIApplication.shared.beginReceivingRemoteControlEvents() // 监听远程控制事件通知 NotificationCenter.default.addObserver(self, selector: #selector(handleRemoteControl), name: UIApplication.didReceiveRemoteControlNotification, object: nil) } @objc private func handleRemoteControl(notification: Notification) { guard let event = notification.userInfo?[UIApplication.remoteControlEventKey] as? UIEvent, let press = event.allPresses?.first else { return } if press.type == .playPause { print("远程播放暂停事件触发") togglePlayPause() } } private func togglePlayPause() { switch avPlayer.timeControlStatus { case .playing: avPlayer.pause() case .paused: avPlayer.play() default: break } } deinit { UIApplication.shared.endReceivingRemoteControlEvents() NotificationCenter.default.removeObserver(self) }
内容的提问来源于stack exchange,提问作者C0D3
相关产品推荐
相关产品推荐

