Capture and play video in an iOS app using AVCaptureMovieFileOutput from AVFoundation for recording and AVPlayerViewController from AVKit for playback. The AVFoundation capture pipeline for video is nearly identical to still image capture. Replace AVCapturePhotoOutput with AVCaptureMovieFileOutput and set the session preset to .high. AVCaptureMovieFileOutput writes video data to a file at a specified URL. AVPlayerViewController wraps an AVPlayer with built-in playback controls for play, pause, scrub, and full-screen mode.
Configure AVCaptureSession for Video Capture
Create an AVCaptureSession with the .high preset and add a camera input and movie file output.
let session = AVCaptureSession()
session.sessionPreset = .high
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: .back),
let input = try? AVCaptureDeviceInput(device: camera) else { return }
let movieOutput = AVCaptureMovieFileOutput()
session.addInput(input)
session.addOutput(movieOutput)
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
DispatchQueue.global(qos: .background).async {
session.startRunning()
}
The .high preset delivers high-quality video. Use .hd1920x1080 for explicit 1080p resolution or .hd4K3840x2160 for 4K on compatible devices. Start the session on a background queue.
Record Video with Start and Stop Controls
Call startRecording(to:recordingDelegate:) to begin and stopRecording() to end.
@IBAction func recordTapped(_ sender: UIButton) {
guard let movieOutput = movieOutput else { return }
if !movieOutput.isRecording {
let url = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mov")
movieOutput.startRecording(to: url, recordingDelegate: self)
animateRecordButton(true)
} else {
movieOutput.stopRecording()
animateRecordButton(false)
}
}
Use UUID().uuidString to create unique filenames for each recording. NSTemporaryDirectory() stores files that iOS can purge. Move important recordings to the Documents directory using FileManager.
Add a Pulsing Animation to the Record Button
Use UIView.animate with .repeat and .autoreverse options.
func animateRecordButton(_ recording: Bool) {
if recording {
UIView.animate(withDuration: 0.5, delay: 0,
options: [.repeat, .autoreverse, .allowUserInteraction],
animations: {
recordButton.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
})
} else {
recordButton.layer.removeAllAnimations()
recordButton.transform = .identity
}
}
The .allowUserInteraction option keeps the button tappable during animation. The scale transform shrinks the button to 60 percent to create a pulsing red dot effect.
Handle Recording Completion with the Delegate
Adopt AVCaptureFileOutputRecordingDelegate to receive the output file URL.
extension CameraViewController: AVCaptureFileOutputRecordingDelegate {
func fileOutput(_ output: AVCaptureFileOutput,
didFinishRecordingTo outputFileURL: URL,
from connections: [AVCaptureConnection],
error: Error?) {
guard error == nil else {
print("Recording error: (error?.localizedDescription ?? "")")
return
}
playVideo(at: outputFileURL)
}
}
The delegate delivers the file URL where the video was saved. Check the error parameter for issues like disk full or recording interruption.
Play Back Video with AVPlayerViewController
Instantiate AVPlayerViewController, assign an AVPlayer with the video URL, and present it.
func playVideo(at url: URL) {
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
present(playerVC, animated: true) {
player.play()
}
}
AVPlayerViewController provides standard playback controls including play, pause, seek, volume, and full-screen toggle. Call player.play() after presentation to start playback immediately.
Customize AVPlayerViewController Behavior
Disable built-in controls for a custom player interface.
playerVC.showsPlaybackControls = false
Enable Picture-in-Picture on iPad.
playerVC.allowsPictureInPicturePlayback = true
Adopt AVPlayerViewControllerDelegate to respond to dismissal and PiP events.
playerVC.delegate = self
extension CameraViewController: AVPlayerViewControllerDelegate {
func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) {
// PiP started
}
}
Choose the Right Session Preset for Video Quality
The .low preset produces small files suitable for sharing over cellular networks. The .medium preset balances quality and size. The .hd1280x720 and .hd1920x1080 presets deliver specific HD resolutions. The .hd4K3840x2160 preset captures 4K video on iPhone 8 and later. The .high preset selects the highest quality available on the device. Match the preset to your app's use case. Social media upload apps benefit from .low or .medium. Video editing apps should use .hd1920x1080 or .hd4K3840x2160.
Record Audio Alongside Video
Add a microphone input to the session for audio recording.
guard let microphone = AVCaptureDevice.default(.builtInMicrophone,
for: .audio,
position: .unspecified),
let audioInput = try? AVCaptureDeviceInput(device: microphone) else { return }
if session.canAddInput(audioInput) {
session.addInput(audioInput)
}
Add NSMicrophoneUsageDescription to Info.plist when recording audio. The .playAndRecord audio session category may also be necessary depending on your playback requirements.
Switch Between Front and Back Cameras for Video
Toggle the camera input during video capture configuration. Stop any active recording before switching.
@objc func switchCamera() {
guard !movieOutput.isRecording else { return }
session.beginConfiguration()
let newPosition: AVCaptureDevice.Position = (currentDevice.position == .back) ? .front : .back
guard let newDevice = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video,
position: newPosition),
let newInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
session.inputs.forEach { session.removeInput($0) }
session.addInput(newInput)
currentDevice = newDevice
session.commitConfiguration()
}
Remove all existing inputs before adding the new camera. Wrap changes in beginConfiguration() and commitConfiguration() for atomic transitions. Check isRecording before switching to avoid interrupting an active recording.
Manage Video File Storage and Cleanup
Temporary video files accumulate quickly. Delete processed recordings to free storage.
func deleteRecording(at url: URL) {
do {
try FileManager.default.removeItem(at: url)
} catch {
print("File deletion failed: (error)")
}
}
Call deleteRecording after the video has been uploaded or exported. Use FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) for recordings the user wants to keep. Store temporary recordings in NSTemporaryDirectory() and permanent ones in the Documents directory.
Export Video in Different Formats
Use AVAssetExportSession to convert recorded video to compressed formats.
let asset = AVAsset(url: outputFileURL)
guard let exportSession = AVAssetExportSession(asset: asset,
presetName: AVAssetExportPresetMediumQuality) else { return }
let exportURL = FileManager.default.temporaryDirectory
.appendingPathComponent("exported.mp4")
exportSession.outputURL = exportURL
exportSession.outputFileType = .mp4
exportSession.exportAsynchronously {
if exportSession.status == .completed {
// Upload exportURL
}
}
AVAssetExportSession supports presets from AVAssetExportPresetLowQuality through AVAssetExportPresetHEVCHighestQuality. MP4 with H.264 compression is the most widely compatible format for server uploads.
Best Practices for Video Capture
Choose the smallest session preset that meets your quality requirements to reduce file size and processing overhead. Clean up temporary files using FileManager.default.removeItem(at:) after upload or playback to prevent storage bloat. Set maxRecordedDuration on AVCaptureMovieFileOutput to limit recording length. Check camera availability with AVCaptureDevice.DiscoverySession before configuring the session. Handle recording interruptions from phone calls by adopting AVCaptureFileOutputRecordingDelegate and prompting users to resume. Test on physical devices only because the simulator does not support real video capture. Monitor CPU and memory usage with Xcode's debug navigator during recording and playback. Use H.264 or HEVC encoding for smaller file sizes when uploading to servers by configuring AVCaptureOutput's supportedOutputSettingsKeys. Add haptic feedback with UIImpactFeedbackGenerator when recording starts and stops for a polished tactile response.
Summary
This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.
Frequently Asked Questions
What Xcode version do I need for this tutorial? This tutorial uses Xcode 26 with Swift 6.2. The code examples work with any recent version of Xcode that supports the frameworks used here.
Can I test this on the iOS Simulator? Some features require a physical device with a compatible processor. The article notes specific sections where the Simulator is sufficient and where a real device is required.
What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.