You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 09:40:38