Swift 文件 URL 开始录制视频时为零
Swift file URL is nil when starting recording a video
当我开始录制视频时,它在隐式解包可选值时抛出“意外发现 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)。问题的原因可能是什么?图片库访问被拒绝是存储权限吗?
每次你在 Swift 中使用 !
时,都会有一只小猫死掉:-)。您的代码具有预期的行为。
您声明:
private weak var videoOutput: AVCaptureMovieFileOutput!
不确定为什么在这里使用 weak,因为这个 class 拥有该对象并且应该保留一个强引用。
然后你在setUpCamera()
中实例化一个本地版本的videoOutput
:
let videoOutput = AVCaptureMovieFileOutput()
但永远不要将其分配给实例 属性 videoOutput
。因此 videoOutput
是 nil
并且你崩溃在
videoOutput.startRecording(to: …)
在 setUpCamera()
添加到捕获会话后分配 属性。
self.videoOutput = videoOutput
或者更好的做法是在 class 声明中创建实例。
private var videoOutput: AVCaptureMovieFileOutput = AVCaptureMovieFileOutput()
当我开始录制视频时,它在隐式解包可选值时抛出“意外发现 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)。问题的原因可能是什么?图片库访问被拒绝是存储权限吗?
每次你在 Swift 中使用 !
时,都会有一只小猫死掉:-)。您的代码具有预期的行为。
您声明:
private weak var videoOutput: AVCaptureMovieFileOutput!
不确定为什么在这里使用 weak,因为这个 class 拥有该对象并且应该保留一个强引用。
然后你在setUpCamera()
中实例化一个本地版本的videoOutput
:
let videoOutput = AVCaptureMovieFileOutput()
但永远不要将其分配给实例 属性 videoOutput
。因此 videoOutput
是 nil
并且你崩溃在
videoOutput.startRecording(to: …)
在 setUpCamera()
添加到捕获会话后分配 属性。
self.videoOutput = videoOutput
或者更好的做法是在 class 声明中创建实例。
private var videoOutput: AVCaptureMovieFileOutput = AVCaptureMovieFileOutput()