在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
相关产品推荐
相关产品推荐

