开始录制视频时 Swift 文件 URL 为零
Posted
技术标签:
【中文标题】开始录制视频时 Swift 文件 URL 为零【英文标题】:Swift file URL is nil when starting recording a video 【发布时间】:2019-10-01 10:51:42 【问题描述】:当我开始录制视频时,它会在隐式展开 Optional value: file 时抛出 Unexpectedly found nil。摄像头和麦克风权限已启用并包含在 Info.plist 中。
/// UIViewController which displays create screen, camera preview.
class CreateViewController: UIViewController
/// Delegate which will receive calls on create view controller camera changes.
var delegate: CreateViewControllerDelegate?
private weak var flashlightCameraButton: UIImageView!
private weak var recordingCameraButton: UIImageView!
private weak var switchCamerasCameraButton: UIImageView!
var viewModel: CreateViewModel?
// MARK: - Camera
private var captureSession: AVCaptureSession!
private weak var previewLayer: CALayer!
private weak var captureCamera: AVCaptureDevice!
private weak var audioDevice: AVCaptureDevice!
private weak var videoOutput: AVCaptureMovieFileOutput!
private var isFlashlightOn = false
private var isRecording = false
private var isFrontCamera = false
/// The URL of the challenge video
var challengeVideoURL: URL?
override func viewDidLoad()
super.viewDidLoad()
setUp()
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
if isRecording
videoOutput.stopRecording()
private func setUp()
setUpMainView()
setUpCamera()
setUpCameraButtons()
private func setUpMainView()
let appWindow = UIApplication.shared.keyWindow
guard let window = appWindow else return
view = UIView(frame: CGRect(x: 0, y: 0, width: window.frame.width, height: window.frame.height))
private func setUpCamera()
let captureSession = AVCaptureSession()
self.captureSession = captureSession
captureSession.sessionPreset = .iFrame1280x720
setUpCameraSide(front: isFrontCamera)
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.previewLayer = previewLayer
view.layer.addSublayer(previewLayer)
previewLayer.frame = view.layer.frame
let videoOutput = AVCaptureMovieFileOutput()
if captureSession.canAddOutput(videoOutput)
captureSession.addOutput(videoOutput)
captureSession.startRunning()
private func setUpCameraSide(front: Bool)
captureSession.beginConfiguration()
if let inputs = captureSession.inputs as? [AVCaptureDeviceInput]
for input in inputs
self.captureSession.removeInput(input)
guard let availableDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: front ? .front : .back) else return
// Prepare visual
self.captureCamera = availableDevice
do
let captureDeviceInput = try AVCaptureDeviceInput(device: captureCamera)
captureSession.addInput(captureDeviceInput)
catch let error
// - TODO: display the error
// Prepare audio
guard let audioDevice = AVCaptureDevice.default(for: .audio) else return
self.audioDevice = audioDevice
do
let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
captureSession.addInput(audioDeviceInput)
catch let error
// - TODO: display the error
captureSession.commitConfiguration()
private func setUpCameraButtons()
let flashlightCameraButton = UIImageView()
self.flashlightCameraButton = flashlightCameraButton
view.addSubview(flashlightCameraButton)
flashlightCameraButton.image = UIImage(named: "camera_flashlight")
let flashlightTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(turnOnFlashlight))
flashlightCameraButton.isUserInteractionEnabled = true
flashlightCameraButton.addGestureRecognizer(flashlightTapGestureRecognizer)
flashlightCameraButton.snp.makeConstraints make in
make.left.equalToSuperview().inset(25)
make.bottom.equalToSuperview().inset(25)
make.height.width.equalTo(50)
let recordingCameraButton = UIImageView()
self.recordingCameraButton = recordingCameraButton
view.addSubview(recordingCameraButton)
recordingCameraButton.image = UIImage(named: "camera_record")
let recordingTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(recording))
recordingCameraButton.isUserInteractionEnabled = true
recordingCameraButton.addGestureRecognizer(recordingTapGestureRecognizer)
recordingCameraButton.snp.makeConstraints make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().inset(25)
make.height.width.equalTo(70)
let switchCamerasCameraButton = UIImageView()
self.switchCamerasCameraButton = switchCamerasCameraButton
view.addSubview(switchCamerasCameraButton)
switchCamerasCameraButton.image = UIImage(named: "camera_switch_camera")
let switchCameraTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(switchCamera))
switchCamerasCameraButton.isUserInteractionEnabled = true
switchCamerasCameraButton.addGestureRecognizer(switchCameraTapGestureRecognizer)
switchCamerasCameraButton.snp.makeConstraints make in
make.right.equalToSuperview().inset(25)
make.bottom.equalToSuperview().inset(25)
make.height.width.equalTo(50)
@objc private func turnOnFlashlight()
isFlashlightOn = !isFlashlightOn
if !isRecording
if !isFrontCamera, captureCamera.hasTorch
do
try captureCamera.lockForConfiguration()
captureCamera.torchMode = captureCamera.isTorchActive ? .on : .off
captureCamera.unlockForConfiguration()
catch let error
// - TODO: handle error here
@objc private func recording()
if !isRecording
delegate?.didStartRecording(self)
NotificationCenter.default.post(name: .recordingStartedNotification, object: nil)
if !captureSession.isRunning
return
let paths = FileManager.default.urls(for: .moviesDirectory, in: .userDomainMask)
let fileURL = paths[0].appendingPathComponent("challenge.mov")
try? FileManager.default.removeItem(at: fileURL)
challengeVideoURL = fileURL
videoOutput.startRecording(to: fileURL, recordingDelegate: self)
else
videoOutput.stopRecording()
isRecording = !isRecording
@objc private func switchCamera()
isFrontCamera = !isFrontCamera
setUpCameraSide(front: isFrontCamera)
private func setUpTimer()
// MARK: - Challenge is recorded
extension CreateViewController: AVCaptureFileOutputRecordingDelegate
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?)
guard error == nil else return
viewModel?.challengeVideoUrl = outputFileURL
UISaveVideoAtPathToSavedPhotosAlbum(outputFileURL.path, nil, nil, nil)
delegate?.didStopRecording(self)
NotificationCenter.default.post(name: .recordingStoppedNotification, object: nil)
// - TODO: after receiving this call moves user to challenge preview
致命错误出现在这一行:videoOutput.startRecording(to: fileURL, recordingDelegate: self)。问题的原因可能是什么?拒绝访问照片库是存储权限吗?
【问题讨论】:
我想知道你是否真的可以写.moviesDirectory
。也许尝试使用临时路径?
我尝试了所有目录,但仍然出现同样的错误。
@Larme 临时路径给出了同样的错误。
【参考方案1】:
每次您在 Swift 中使用 !
时,都会有一只小猫死亡 :-)。您的代码具有预期的行为。
您声明:
private weak var videoOutput: AVCaptureMovieFileOutput!
不知道你为什么在这里使用weak,因为这个类拥有对象并且应该保持一个强引用。
然后在setUpCamera()
中实例化videoOutput
的本地 版本:
let videoOutput = AVCaptureMovieFileOutput()
但永远不要将其分配给实例属性videoOutput
。因此videoOutput
是nil
并且你在“崩溃”
videoOutput.startRecording(to: …)
在setUpCamera()
在添加到捕获会话之后分配属性。
self.videoOutput = videoOutput
或者最好在类声明中创建实例。
private var videoOutput: AVCaptureMovieFileOutput = AVCaptureMovieFileOutput()
【讨论】:
酷,它有效,保持弱引用可能是我的错误,但无论如何它有所帮助,现在它完美无缺。真的非常感谢沃伦。 太棒了。weak
是我故意使用的东西,通常在闭包和委托引用中保留循环。主要问题是所有权变更和 ARC 系统可以随时使弱引用为零。将其与强制解包结合起来,您可能会遇到未来的错误。以上是关于开始录制视频时 Swift 文件 URL 为零的主要内容,如果未能解决你的问题,请参考以下文章
Swift IOS 使用 AVFoundation 录制视频和音频
从 URL 播放视频并同时通过摄像头录制视频 - Objective C