iOS ReplayKit用户选择「不允许」录屏时无回调问题排查与解决
ReplayKit用户选择「不允许」无回调的原因及解决方案
原因
ReplayKit的startCapture方法存在设计特性:当用户首次面对权限弹窗选择不允许时,系统不会通过传入的handler回调返回错误。这是因为此时录制流程并未真正启动,权限弹窗的拒绝操作属于权限请求环节,不属于录制启动失败的场景,因此不会触发startCapture的错误回调逻辑。只有当录制流程尝试启动但因已拒绝的权限、系统限制等问题失败时,才会通过错误回调返回信息。
解决方案
针对这个问题,根据iOS版本不同,有以下几种可靠的处理方式:
方案1:使用异步/await版本的startCapture(iOS 14+)
iOS 14及以上提供了异步/await版本的startCaptureAPI,该版本在用户拒绝权限时会明确抛出RPRecordingError.userDeclined错误,直接捕获即可感知用户操作:
static private func startCapture(callback:@escaping(_ code:ScreenCheckStatus,_ msg:String) ->Void) { RPScreenRecorder.shared().isMicrophoneEnabled = true isCheckAuthAlreadStop = false Task { do { let captureHandler: (CMSampleBuffer, RPSampleBufferType) -> Void = { buffer, bufType in self.stopListenRefuseRecord() if buffer.isValid && CMSampleBufferDataIsReady(buffer) && !self.isCheckAuthAlreadStop { self.stopCapture(callback: callback) self.isCheckAuthAlreadStop = true } } try await RPScreenRecorder.shared().startCapture(handler: captureHandler) } catch let error as NSError { self.stopListenRefuseRecord() if error.code == RPRecordingError.userDeclined.rawValue { callback(.resufeRecordError,"用户拒绝屏幕录制,请重新启动app,否则将导致屏幕录制失败") } else { callback(.startRecordError, error.localizedDescription) } } } }
方案2:兼容iOS 14以下:超时检测+权限状态检查
对于低版本系统,可通过超时定时器结合权限状态检查来处理:用户拒绝弹窗后,startCapture的handler不会触发,超时后即可判定为用户拒绝;同时可提前检查权限状态,避免重复弹窗:
static private func startCapture(callback:@escaping(_ code:ScreenCheckStatus,_ msg:String) ->Void) { RPScreenRecorder.shared().isMicrophoneEnabled = true isCheckAuthAlreadStop = false // 超时检测:5秒内未收到回调则判定为用户拒绝 let timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { _ in guard !self.isCheckAuthAlreadStop else { return } callback(.resufeRecordError,"用户拒绝屏幕录制,请重新启动app,否则将导致屏幕录制失败") self.stopListenRefuseRecord() } RunLoop.main.add(timeoutTimer, forMode: .common) // 提前检查权限(iOS 15+可用) if #available(iOS 15.0, *) { let status = RPScreenRecorder.shared().authorizationStatus switch status { case .denied, .restricted: timeoutTimer.invalidate() let msg = status == .denied ? "用户拒绝屏幕录制,请重新启动app,否则将导致屏幕录制失败" : "系统限制屏幕录制" callback(status == .denied ? .resufeRecordError : .startRecordError, msg) return case .authorized: // 已授权,直接执行后续逻辑 break case .notDetermined: // 未弹窗,继续执行 break @unknown default: break } } startListenRefuseRecord(callback: callback) RPScreenRecorder.shared().startCapture { buffer, bufType, error in timeoutTimer.invalidate() // 收到回调立即取消超时 if let error = error { callback(.startRecordError, error.localizedDescription) self.stopListenRefuseRecord() } else { self.stopListenRefuseRecord() if buffer.isValid && CMSampleBufferDataIsReady(buffer) && !self.isCheckAuthAlreadStop { self.stopCapture(callback: callback) self.isCheckAuthAlreadStop = true } } } }
优化原有Combine监听逻辑(可选)
原有Combine监听的状态组合逻辑不准确,用户拒绝后isMicrophoneEnabled仍为true,导致!$0 && !$1不会触发回调。可调整为监听authorizationStatus(iOS 15+):
@available(iOS 15.0, *) static private func startListenRefuseRecord(callback:@escaping(_ code:ScreenCheckStatus,_ msg:String) ->Void) { ScreenCheck.isUserRefuseRecord = false let statusPublisher = RPScreenRecorder.shared().publisher(for: \.authorizationStatus) let cancelBag = Subscribers.Sink<RPAuthStatus, Never>( receiveCompletion: { _ in }, receiveValue: { status in guard !ScreenCheck.isUserRefuseRecord else { return } if status == .denied { ScreenCheck.isUserRefuseRecord = true callback(.resufeRecordError,"用户拒绝屏幕录制,请重新启动app,否则将导致屏幕录制失败") ScreenCheck.stopListenRefuseRecord() } } ) statusPublisher.subscribe(cancelBag) self.userRefuseRecordCancelBag = cancelBag }
内容的提问来源于stack exchange,提问作者boy lucky
相关产品推荐
相关产品推荐

