Ответ 1
Я собираюсь упростить вам задачу, разместив весь код, необходимый для создания видеомагнитофона в AVFoundation. Этот код должен работать, если вы просто скопируете и вставите его как есть. Единственное, что вам нужно помнить, это то, что вам нужно подключить выход camPreview к UIView в ViewController в StoryBoard. Этот UIView должен занимать все содержимое экрана. Я объясню код, чтобы вы могли провести собственное расследование и изменить видеорегистратор в соответствии с потребностями своего приложения. Вам также необходимо убедиться, что вы прикрепили соответствующие разрешения на конфиденциальность к info.plist, которые являются Конфиденциальностью - Описание использования микрофона и Конфиденциальностью - Описание использования камеры, иначе вы увидите только черный экран.
ПРИМЕЧАНИЕ. Справа внизу я добавил способ воспроизведения записанного видео под заголовком "Воспроизведение записанного видео".
РЕДАКТИРОВАТЬ - Я забыл две вещи, которые привели к сбою во время записи, но я добавил их сейчас.
Swift 4
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
@IBOutlet weak var camPreview: UIView!
let cameraButton = UIView()
let captureSession = AVCaptureSession()
let movieOutput = AVCaptureMovieFileOutput()
var previewLayer: AVCaptureVideoPreviewLayer!
var activeInput: AVCaptureDeviceInput!
var outputURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
if setupSession() {
setupPreview()
startSession()
}
cameraButton.isUserInteractionEnabled = true
let cameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.startCapture))
cameraButton.addGestureRecognizer(cameraButtonRecognizer)
cameraButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
cameraButton.backgroundColor = UIColor.red
camPreview.addSubview(cameraButton)
}
func setupPreview() {
// Configure previewLayer
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = camPreview.bounds
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
camPreview.layer.addSublayer(previewLayer)
}
//MARK:- Setup Camera
func setupSession() -> Bool {
captureSession.sessionPreset = AVCaptureSession.Preset.high
// Setup Camera
let camera = AVCaptureDevice.default(for: AVMediaType.video)!
do {
let input = try AVCaptureDeviceInput(device: camera)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
activeInput = input
}
} catch {
print("Error setting device video input: \(error)")
return false
}
// Setup Microphone
let microphone = AVCaptureDevice.default(for: AVMediaType.audio)!
do {
let micInput = try AVCaptureDeviceInput(device: microphone)
if captureSession.canAddInput(micInput) {
captureSession.addInput(micInput)
}
} catch {
print("Error setting device audio input: \(error)")
return false
}
// Movie output
if captureSession.canAddOutput(movieOutput) {
captureSession.addOutput(movieOutput)
}
return true
}
func setupCaptureMode(_ mode: Int) {
// Video Mode
}
//MARK:- Camera Session
func startSession() {
if !captureSession.isRunning {
videoQueue().async {
self.captureSession.startRunning()
}
}
}
func stopSession() {
if captureSession.isRunning {
videoQueue().async {
self.captureSession.stopRunning()
}
}
}
func videoQueue() -> DispatchQueue {
return DispatchQueue.main
}
func currentVideoOrientation() -> AVCaptureVideoOrientation {
var orientation: AVCaptureVideoOrientation
switch UIDevice.current.orientation {
case .portrait:
orientation = AVCaptureVideoOrientation.portrait
case .landscapeRight:
orientation = AVCaptureVideoOrientation.landscapeLeft
case .portraitUpsideDown:
orientation = AVCaptureVideoOrientation.portraitUpsideDown
default:
orientation = AVCaptureVideoOrientation.landscapeRight
}
return orientation
}
@objc func startCapture() {
startRecording()
}
//EDIT 1: I FORGOT THIS AT FIRST
func tempURL() -> URL? {
let directory = NSTemporaryDirectory() as NSString
if directory != "" {
let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
return URL(fileURLWithPath: path)
}
return nil
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! VideoPlaybackViewController
vc.videoURL = sender as? URL
}
func startRecording() {
if movieOutput.isRecording == false {
let connection = movieOutput.connection(with: AVMediaType.video)
if (connection?.isVideoOrientationSupported)! {
connection?.videoOrientation = currentVideoOrientation()
}
if (connection?.isVideoStabilizationSupported)! {
connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
}
let device = activeInput.device
if (device.isSmoothAutoFocusSupported) {
do {
try device.lockForConfiguration()
device.isSmoothAutoFocusEnabled = false
device.unlockForConfiguration()
} catch {
print("Error setting configuration: \(error)")
}
}
//EDIT2: And I forgot this
outputURL = tempURL()
movieOutput.startRecording(to: outputURL, recordingDelegate: self)
}
else {
stopRecording()
}
}
func stopRecording() {
if movieOutput.isRecording == true {
movieOutput.stopRecording()
}
}
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
}
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if (error != nil) {
print("Error recording movie: \(error!.localizedDescription)")
} else {
let videoRecorded = outputURL! as URL
performSegue(withIdentifier: "showVideo", sender: videoRecorded)
}
}
}
Свифт 3
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
@IBOutlet weak var camPreview: UIView!
let cameraButton = UIView()
let captureSession = AVCaptureSession()
let movieOutput = AVCaptureMovieFileOutput()
var previewLayer: AVCaptureVideoPreviewLayer!
var activeInput: AVCaptureDeviceInput!
var outputURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
if setupSession() {
setupPreview()
startSession()
}
cameraButton.isUserInteractionEnabled = true
let cameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.startCapture))
cameraButton.addGestureRecognizer(cameraButtonRecognizer)
cameraButton.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
cameraButton.backgroundColor = UIColor.red
camPreview.addSubview(cameraButton)
}
func setupPreview() {
// Configure previewLayer
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = camPreview.bounds
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
camPreview.layer.addSublayer(previewLayer)
}
//MARK:- Setup Camera
func setupSession() -> Bool {
captureSession.sessionPreset = AVCaptureSessionPresetHigh
// Setup Camera
let camera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
let input = try AVCaptureDeviceInput(device: camera)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
activeInput = input
}
} catch {
print("Error setting device video input: \(error)")
return false
}
// Setup Microphone
let microphone = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio)
do {
let micInput = try AVCaptureDeviceInput(device: microphone)
if captureSession.canAddInput(micInput) {
captureSession.addInput(micInput)
}
} catch {
print("Error setting device audio input: \(error)")
return false
}
// Movie output
if captureSession.canAddOutput(movieOutput) {
captureSession.addOutput(movieOutput)
}
return true
}
func setupCaptureMode(_ mode: Int) {
// Video Mode
}
//MARK:- Camera Session
func startSession() {
if !captureSession.isRunning {
videoQueue().async {
self.captureSession.startRunning()
}
}
}
func stopSession() {
if captureSession.isRunning {
videoQueue().async {
self.captureSession.stopRunning()
}
}
}
func videoQueue() -> DispatchQueue {
return DispatchQueue.main
}
func currentVideoOrientation() -> AVCaptureVideoOrientation {
var orientation: AVCaptureVideoOrientation
switch UIDevice.current.orientation {
case .portrait:
orientation = AVCaptureVideoOrientation.portrait
case .landscapeRight:
orientation = AVCaptureVideoOrientation.landscapeLeft
case .portraitUpsideDown:
orientation = AVCaptureVideoOrientation.portraitUpsideDown
default:
orientation = AVCaptureVideoOrientation.landscapeRight
}
return orientation
}
func startCapture() {
startRecording()
}
//EDIT 1: I FORGOT THIS AT FIRST
func tempURL() -> URL? {
let directory = NSTemporaryDirectory() as NSString
if directory != "" {
let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
return URL(fileURLWithPath: path)
}
return nil
}
func startRecording() {
if movieOutput.isRecording == false {
let connection = movieOutput.connection(withMediaType: AVMediaTypeVideo)
if (connection?.isVideoOrientationSupported)! {
connection?.videoOrientation = currentVideoOrientation()
}
if (connection?.isVideoStabilizationSupported)! {
connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto
}
let device = activeInput.device
if (device?.isSmoothAutoFocusSupported)! {
do {
try device?.lockForConfiguration()
device?.isSmoothAutoFocusEnabled = false
device?.unlockForConfiguration()
} catch {
print("Error setting configuration: \(error)")
}
}
//EDIT2: And I forgot this
outputURL = tempURL()
movieOutput.startRecording(toOutputFileURL: outputURL, recordingDelegate: self)
}
else {
stopRecording()
}
}
func stopRecording() {
if movieOutput.isRecording == true {
movieOutput.stopRecording()
}
}
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
}
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
if (error != nil) {
print("Error recording movie: \(error!.localizedDescription)")
} else {
_ = outputURL as URL
}
outputURL = nil
}
}
Вот как вы должны настроить контроллер представления
Разрешения для вашего Info.plist
Настройка записи делегатов
Вам необходимо соответствовать AVCaptureFileOutputRecordingDelegate. Согласно документам Apple, он определяет интерфейс для делегатов AVCaptureFileOutput, чтобы реагировать на события, которые происходят в процессе записи одного файла. Он поставляется с двумя методами, которые необходимо реализовать, и это последние два метода в нижней части кода. Во-первых,
func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) {
}
Вы можете добавить любую логику к этому, когда видео начинает запись. В приведенном мной примере кода видео начинает записываться, когда вы нажимаете красную квадратную кнопку в левом углу. Во-вторых,
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
if (error != nil) {
print("Error recording movie: \(error!.localizedDescription)")
} else {
_ = outputURL as URL
}
outputURL = nil
}
Это вызывается, когда видео закончило запись. В примере кода, который я дал, видео перестает записываться после повторного нажатия на красную квадратную кнопку. Когда видео прекратило запись, вы получите URL выходного файла. Это представляет ваше видео. Вы можете использовать это, чтобы, возможно, перейти к другому View Controller для воспроизведения видео в AVPlayer. Или вы можете сохранить его. В этом примере вы заметите, что я не так много сделал с выходным URL.
Чтобы начать запись видео, я использовал программно созданную кнопку, которая отображается в виде красного квадрата в левом углу и отвечает на UITapGesture. Вы можете сделать лучшую кнопку в своем приложении.
Настройка сеанса
Видеорегистратору нужен сеанс захвата, который я настроил в setupSession(). Здесь вы добавляете устройства ввода AVCapture, которые включают камеру и микрофон. Согласно Apple, AVCaptureDeviceInput является конкретным подклассом AVCaptureInput, который вы используете для захвата данных из объекта AVCaptureDevice. Тем не менее, пользователь должен предоставить вам доступ к их использованию, поэтому в вашем info.plist вы должны добавить Конфиденциальность - Описание использования микрофона и Конфиденциальность - Описание использования камеры и указать причину, по которой вы хотите использовать видеомагнитофон и микрофон. Если вы этого не сделаете, вы получите только черный экран. Предварительная установка сеанса является постоянным значением, указывающим уровень качества или битрейт вывода. Я установил это на высокий уровень, но есть и другие варианты, которые вы можете изучить. Аргумент movieOutput имеет тип AVCaptureMovieFileOutput, который, согласно Apple, является конкретным подклассом AVCaptureFileOutput, который используется для захвата данных в фильм QuickTime. Это то, что на самом деле позволяет вам записывать и сохранять видео.
Настройка предварительного просмотра
Здесь вы настраиваете слой предварительного просмотра камеры, что делается в setupPreview(). Вы устанавливаете слой предварительного просмотра с созданным вами сеансом захвата с помощью следующего AVCaptureVideoPreviewLayer (session: captureSession).
Начиная сессию
Последний шаг - запустить сеанс, который выполняется в startSession(). Вы проверяете, запущен ли уже сеанс, а если нет, то запускаете его.
if !captureSession.isRunning {
videoQueue().async {
self.captureSession.startRunning()
}
}
Начало записи
Когда вы нажимаете красную кнопку, вызывается метод startRecording(). Здесь я добавил методы для обработки ориентации видео и стабилизации видео. Наконец, мы снова видим переменную movieOutput, которую мы установили ранее в нашем сеансе. Мы вызываем его для записи нашего фильма в outputURL и говорим, что наши методы-делегаты для обработки начала и конца записи находятся в одном контроллере представления (эти два последних метода).
Остановить запись
Так уж получилось, что когда вы снова нажмете красную кнопку, снова вызывается startRecoding, но он заметит, что что-то записывается, и вызовет stopRecording.
Воспроизведение записанного видео
Я щедрый сегодня, поэтому я добавлю это тоже.
Создайте новый контроллер представления и назовите его VideoPlayback. Подключите его к своему первому ViewController, используя переход в раскадровке. Дайте Sege идентификатор "showVideo". Создайте UIView и заполните экран VideoPlayback и создайте выход для его контроллера представления, называемого videoView. Добавьте следующий код в ваш новый контроллер представления VideoPlayback:
Swift 4
import UIKit
import AVFoundation
class VideoPlayback: UIViewController {
let avPlayer = AVPlayer()
var avPlayerLayer: AVPlayerLayer!
var videoURL: URL!
//connect this to your uiview in storyboard
@IBOutlet weak var videoView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.frame = view.bounds
avPlayerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoView.layer.insertSublayer(avPlayerLayer, at: 0)
view.layoutIfNeeded()
let playerItem = AVPlayerItem(url: videoURL as URL)
avPlayer.replaceCurrentItem(with: playerItem)
avPlayer.play()
}
}
Свифт 3
import UIKit
import AVFoundation
class VideoPlayback: UIViewController {
let avPlayer = AVPlayer()
var avPlayerLayer: AVPlayerLayer!
var videoURL: URL!
//connect this to your uiview in storyboard
@IBOutlet weak var videoView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
avPlayerLayer = AVPlayerLayer(player: avPlayer)
avPlayerLayer.frame = view.bounds
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
videoView.layer.insertSublayer(avPlayerLayer, at: 0)
view.layoutIfNeeded()
let playerItem = AVPlayerItem(url: videoURL as URL)
avPlayer.replaceCurrentItem(with: playerItem)
avPlayer.play()
}
}
Теперь вернитесь к последнему методу делегата и измените его следующим образом:
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) {
if (error != nil) {
print("Error recording movie: \(error!.localizedDescription)")
} else {
let videoRecorded = outputURL! as URL
performSegue(withIdentifier: "showVideo", sender: videoRecorded)
}
}
Наконец, создайте метод подготовки к переходу, который инициализирует videoURL, который будет воспроизводиться с AVPlayer.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! VideoPlayback
vc.videoURL = sender as! URL
}
Теперь, чтобы проверить, вернитесь и начните запись видео. При втором касании красного квадрата будет выполнена передача, и вы увидите, что записанное видео воспроизводится автоматически.