Lan*_*ria 0 avfoundation ios avcapturesession swift
这个问题在 Xcode 10.2.1 和 iOS 12 中没有出现。它在 Xcode 11.1 和 iOS 13 中开始
我的应用程序录制视频,当应用程序转到后台时,我停止运行捕获会话并删除预览层。当应用程序返回前台时,我重新启动捕获会话并将预览层添加回:
let captureSession = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer?
var movieFileOutput = AVCaptureMovieFileOutput()
// *** I initially didn't remove the preview layer in this example but I did remove it in the other 2 examples below ***
@objc fileprivate func stopCaptureSession() {
DispatchQueue.main.async {
[weak self] in
if self?.captureSession.isRunning == true {
self?.captureSession.stopRunning()
}
}
}
@objc func restartCaptureSession() {
DispatchQueue.main.async {
[weak self] in
if self?.captureSession.isRunning == false {
self?.captureSession.startRunning()
}
}
}
Run Code Online (Sandbox Code Playgroud)
发生的情况是,当我转到后台并返回预览层时,用户界面完全冻结。但是在进入后台之前,如果我在线上放置一个断点if self?.captureSession.isRunning == true
并在线上放置另一个断点if self?.captureSession.isRunning == false
,一旦我触发断点,预览层和 ui 就可以正常工作。
经过进一步研究,我发现了这个问题,并在评论中 @HotLicks 说:
Obviously, it's likely that the breakpoint gives time for some async activity to complete before the above code starts mucking with things. However, it's also the case that 0.03 seconds is an awfully short repeat interval for a timer, and it may simply be the case that the breakpoint allows the UI setup to proceed before the timer ties up the CPU.
Run Code Online (Sandbox Code Playgroud)
我做了更多研究,苹果说:
startRunning() 方法是一个阻塞调用,可能需要一些时间,因此您应该在串行队列上执行会话设置,以便主队列不会被阻塞(这会保持 UI 响应)。有关实现示例,请参阅 AVCam-iOS:使用 AVFoundation 捕获图像和电影。
使用 @HotLicks 的评论和 Apple 的信息,我切换到使用DispatchQueue.main.sync
,然后Dispatch Group
从后台返回后,预览层和 ui 仍然冻结。但是,一旦我像第一个示例中那样添加断点并触发它们,预览层和用户界面就可以正常工作。
我究竟做错了什么?
更新
我从调试模式切换到发布模式,但仍然不起作用。
我也尝试切换到使用@MohyG 建议的DispatchQueue.global(qos: .background).async
计时器DispatchQueue.main.asyncAfter(deadline: .now() + 1.5)
,但这没有什么区别。
在没有断点的情况下进一步检查后,后台通知工作正常,但当应用程序进入 fg 时,前台通知不会被调用。由于某种原因,仅当我第一次在函数内放置断点时才会触发 fg 通知stopCaptureSession()
。
问题是前台通知仅在我上面描述的断点处触发。
我尝试了 DispatchQueue.main.sync:
@objc fileprivate func stopCaptureSession() {
if captureSession.isRunning { // adding a breakpoint here is the only thing that triggers the foreground notification when the the app comes back
DispatchQueue.global(qos: .default).async {
[weak self] in
DispatchQueue.main.sync {
self?.captureSession.stopRunning()
}
DispatchQueue.main.async {
self?.previewLayer?.removeFromSuperlayer()
self?.previewLayer = nil
}
}
}
}
@objc func restartCaptureSession() {
if !captureSession.isRunning {
DispatchQueue.global(qos: .default).async {
[weak self] in
DispatchQueue.main.sync {
self?.captureSession.startRunning()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self?.previewLayer = AVCaptureVideoPreviewLayer(session: self!.captureSession)
self?.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
guard let previewLayer = self?.previewLayer else { return }
previewLayer.frame = self!.containerViewForPreviewLayer.bounds
self?.containerViewForPreviewLayer.layer.insertSublayer(previewLayer, at: 0)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我尝试了调度组:
@objc fileprivate func stopCaptureSession() {
let group = DispatchGroup()
if captureSession.isRunning { // adding a breakpoint here is the only thing that triggers the foreground notification when the the app comes back
group.enter()
DispatchQueue.global(qos: .default).async {
[weak self] in
self?.captureSession.stopRunning()
group.leave()
group.notify(queue: .main) {
self?.previewLayer?.removeFromSuperlayer()
self?.previewLayer = nil
}
}
}
}
@objc func restartCaptureSession() {
let group = DispatchGroup()
if !captureSession.isRunning {
group.enter()
DispatchQueue.global(qos: .default).async {
[weak self] in
self?.captureSession.startRunning()
group.leave()
group.notify(queue: .main) {
self?.previewLayer = AVCaptureVideoPreviewLayer(session: self!.captureSession)
self?.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
guard let previewLayer = self?.previewLayer else { return }
previewLayer.frame = self!.containerViewForPreviewLayer.bounds
self?.containerViewForPreviewLayer.layer.insertSublayer(previewLayer, at: 0)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果需要的话,这是其余的代码:
NotificationCenter.default.addObserver(self, selector: #selector(appHasEnteredBackground),
name: UIApplication.willResignActiveNotification,
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(sessionWasInterrupted),
name: .AVCaptureSessionWasInterrupted,
object: captureSession)
NotificationCenter.default.addObserver(self, selector: #selector(sessionInterruptionEnded),
name: .AVCaptureSessionInterruptionEnded,
object: captureSession)
NotificationCenter.default.addObserver(self, selector: #selector(sessionRuntimeError),
name: .AVCaptureSessionRuntimeError,
object: captureSession)
func stopMovieShowControls() {
if movieFileOutput.isRecording {
movieFileOutput.stopRecording()
}
recordButton.isHidden = false
saveButton.isHidden = false
}
@objc fileprivate func appWillEnterForeground() {
restartCaptureSession()
}
@objc fileprivate func appHasEnteredBackground() {
stopMovieShowControls()
imagePicker.dismiss(animated: false, completion: nil)
stopCaptureSession()
}
@objc func sessionRuntimeError(notification: NSNotification) {
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else { return }
stopMovieRecordigShowControls()
if error.code == .mediaServicesWereReset {
if !captureSession.isRunning {
DispatchQueue.main.async { [weak self] in
self?.captureSession.startRunning()
}
} else {
restartCaptureSession()
}
} else {
restartCaptureSession()
}
}
@objc func sessionWasInterrupted(notification: NSNotification) {
if let userInfoValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as AnyObject?,
let reasonIntegerValue = userInfoValue.integerValue,
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
switch reason {
case .videoDeviceNotAvailableInBackground:
stopMovieShowControls()
case .audioDeviceInUseByAnotherClient, .videoDeviceInUseByAnotherClient:
stopMovieShowControls()
case .videoDeviceNotAvailableWithMultipleForegroundApps:
print("2. The toggleButton was pressed")
case .videoDeviceNotAvailableDueToSystemPressure:
// no documentation
break
@unknown default:
break
}
}
}
@objc func sessionInterruptionEnded(notification: NSNotification) {
restartCaptureSession()
stopMovieShowControls()
}
Run Code Online (Sandbox Code Playgroud)
你有没有尝试过DispatchQueue.global(qos: .background).async
?self?.captureSession.startRunning()
基本上从我得到的情况来看,你需要在和之前造成延迟self?.captureSession.stopRunning()
。解决您的问题的一个快速解决方案是使用手动延迟,如下所示:
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
但不建议
您可以尝试一下,看看它是否可以解决您的问题,如果是这样,您需要处理应用程序转换状态AppDelegate
基本上,当您转换到后台和前台时,您需要以某种方式管理触发您的captureSession
开始/停止AppDelegate
:
func applicationDidEnterBackground(_ application: UIApplication) {}
和
func applicationDidBecomeActive(_ application: UIApplication) {}
归档时间: |
|
查看次数: |
13458 次 |
最近记录: |