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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 17:55:20