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

在SwiftUI中能否捕获VideoPlayer的播放、暂停及滑块拖拽等用户事件?

SwiftUI VideoPlayer 播放交互事件监听方案

你要的三类交互事件完全可以实现,分两种实现路径,根据你是否要保留系统原生播放控制条选择:

方案一:自定义播放控制条(推荐,无兼容风险)

直接关闭系统原生控制条,自己实现播放/暂停按钮和进度滑块,所有交互事件完全可控,不需要处理系统私有逻辑:

import SwiftUI
import AVKit

struct CustomVideoPlayerView: View {
    // 提前初始化AVPlayer,不要在body内创建避免重复实例化
    @State private var player = AVPlayer(url: URL(string: "https://vid.tasty.co/output/217191/hls24_1632510042.m3u8")!)
    @State private var isPlaying = false
    @State private var currentTime: Double = 0
    @State private var totalTime: Double = 0
    @State private var isDraggingSlider = false
    @State private var rateBeforeDrag: Float = 0
    // 每0.5秒更新一次播放进度
    private let timeObserverInterval = CMTimeMakeWithSeconds(0.5, preferredTimescale: 1)
    
    var body: some View {
        VStack(spacing: 16) {
            VideoPlayer(player: player)
                .disabled(true) // 禁用系统默认交互
                .onAppear {
                    // 获取视频总时长
                    if let item = player.currentItem {
                        totalTime = item.duration.seconds
                    }
                    // 添加进度监听
                    player.addPeriodicTimeObserver(forInterval: timeObserverInterval, queue: .main) { time in
                        guard !isDraggingSlider else { return }
                        currentTime = time.seconds
                    }
                }
            
            // 自定义进度条
            Slider(value: $currentTime, in: 0...totalTime) { editing in
                isDraggingSlider = editing
                if editing {
                    rateBeforeDrag = player.rate
                    player.pause()
                    // 触发用户开始拖拽进度条事件
                    print("用户开始拖拽进度条")
                } else {
                    // 拖拽结束后跳转进度
                    player.seek(to: CMTimeMakeWithSeconds(currentTime, preferredTimescale: 1)) { _ in
                        // 恢复拖拽前的播放状态
                        if rateBeforeDrag > 0 {
                            player.play()
                        }
                        // 触发用户结束拖拽进度条事件
                        print("用户结束拖拽进度条")
                    }
                }
            }
            .padding(.horizontal)
            
            // 自定义播放暂停按钮
            Button {
                if isPlaying {
                    player.pause()
                    // 触发用户主动暂停事件
                    print("用户主动暂停播放")
                } else {
                    player.play()
                    // 触发用户主动播放事件
                    print("用户主动开始播放")
                }
                isPlaying.toggle()
            } label: {
                Image(systemName: isPlaying ? "pause.fill" : "play.fill")
                    .font(.largeTitle)
            }
        }
        .ignoresSafeArea(.all, edges: .top)
    }
}

方案二:保留系统原生控制条

如果需要保留系统自带的控制条样式,可以通过UIViewRepresentable封装AVPlayerViewController,结合手势识别和KVO来区分三类事件:

import SwiftUI
import AVKit

struct SystemControlVideoPlayer: UIViewControllerRepresentable {
    let player: AVPlayer
    // 事件回调
    var onUserPlay: () -> Void
    var onUserPause: () -> Void
    var onBeginDragProgress: () -> Void
    var onEndDragProgress: () -> Void
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let vc = AVPlayerViewController()
        vc.player = player
        vc.delegate = context.coordinator
        
        // 添加手势识别,拦截控制条区域的拖拽操作
        let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handlePanGesture(_:)))
        panGesture.cancelsTouchesInView = false
        vc.view.addGestureRecognizer(panGesture)
        
        // KVO监听播放速率变化
        context.coordinator.rateObservation = player.observe(\.rate, options: [.new, .old]) { [weak context] _, change in
            guard let coordinator = context?.coordinator,
                  let newRate = change.newValue,
                  let oldRate = change.oldValue,
                  newRate != oldRate,
                  !coordinator.isDraggingProgress else { return }
            // 非拖拽状态下的速率变化判定为用户主动播放/暂停
            newRate > 0 ? onUserPlay() : onUserPause()
        }
        return vc
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, AVPlayerViewControllerDelegate {
        var parent: SystemControlVideoPlayer
        var rateObservation: NSKeyValueObservation?
        var isDraggingProgress = false
        
        init(_ parent: SystemControlVideoPlayer) {
            self.parent = parent
        }
        
        @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
            // 判断手势是否落在底部控制条区域
            let viewHeight = gesture.view?.bounds.height ?? 0
            let touchY = gesture.location(in: gesture.view).y
            let controlBarAreaHeight: CGFloat = 80
            guard touchY > viewHeight - controlBarAreaHeight else { return }
            
            switch gesture.state {
            case .began:
                isDraggingProgress = true
                parent.onBeginDragProgress()
            case .ended, .cancelled, .failed:
                isDraggingProgress = false
                parent.onEndDragProgress()
            default: break
            }
        }
    }
}

// 调用示例
struct DemoView: View {
    @State private var player = AVPlayer(url: URL(string: "https://vid.tasty.co/output/217191/hls24_1632510042.m3u8")!)
    
    var body: some View {
        SystemControlVideoPlayer(
            player: player,
            onUserPlay: { print("用户主动播放") },
            onUserPause: { print("用户主动暂停") },
            onBeginDragProgress: { print("用户开始拖拽进度条") },
            onEndDragProgress: { print("用户结束拖拽进度条") }
        )
        .ignoresSafeArea()
    }
}

注意:该方案依赖系统控制条的固定布局,iOS版本迭代时可能出现控制条位置变化导致手势判断失效,上线项目优先选择方案一。

内容的提问来源于stack exchange,提问作者Tamás Horváth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 12:36:02