AVFoundation provides comprehensive video processing capabilities. Learn to play, edit, and process videos.
AVPlayer Setup
Basic Video Player
swift
import AVFoundation
import AVKit
import SwiftUI
struct VideoPlayerView: View {
@StateObject private var player = VideoPlayer()
var body: some View {
VStack {
VideoPlayer(player: player.player)
.onAppear {
player.play()
}
.onDisappear {
player.pause()
}
}
}
}
class VideoPlayer: NSObject, ObservableObject {
@Published var isPlaying = false
@Published var currentTime: Double = 0
@Published var duration: Double = 0
var player: AVPlayer
override init() {
super.init()
guard let url = URL(string: "https://example.com/video.mp4") else {
fatalError("Invalid URL")
}
player = AVPlayer(url: url)
// Add periodic observer
let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
guard let self = self else { return }
self?.currentTime = CMTimeGetSeconds(time)
}
// Observe player status
player.addObserver(self, forKeyPath: "timeControlStatus", options: .new)
// Observe duration
let asset = AVAsset(url: url)
asset.loadValuesAsynchronously(forKeys: ["duration"], completionHandler: { loaded, error in
guard loaded, error == nil else { return }
let duration = try? asset.duration
self?.duration = CMTimeGetSeconds(duration ?? CMTime())
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "timeControlStatus" {
DispatchQueue.main.async { [weak self] in
self?.updatePlayingStatus()
}
}
}
func play() {
player.play()
isPlaying = true
}
func pause() {
player.pause()
isPlaying = false
}
func stop() {
player.pause()
player.replaceCurrentItem(with: nil)
isPlaying = false
}
func seek(to seconds: Double) {
let time = CMTime(seconds: seconds, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
player.seek(to: time)
}
private func updatePlayingStatus() {
isPlaying = player.timeControlStatus == .playing
}
deinit {
player.removeTimeObserver(self)
player.removeObserver(self, forKeyPath: "timeControlStatus")
}
}Video Composition
AVFoundation Editing
swift
import AVFoundation
class VideoEditor {
var asset: AVAsset
var composition: AVMutableComposition?
init(asset: AVAsset) {
self.asset = asset
}
func createComposition() -> AVMutableComposition {
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
do {
try videoTrack.insertTimeRange(
CMTimeRange(start: .zero, duration: asset.duration),
of: asset.tracks(withMediaType: .video)[0],
at: .zero
)
try audioTrack.insertTimeRange(
CMTimeRange(start: .zero, duration: asset.duration),
of: asset.tracks(withMediaType: .audio)[0],
at: .zero
)
} catch {
print("Error creating composition: \(error)")
}
self.composition = composition
return composition
}
func trimVideo(from startTime: Double, to endTime: Double) -> AVAsset? {
guard let composition = composition else { return nil }
let start = CMTime(seconds: startTime, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
let end = CMTime(seconds: endTime, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
let range = CMTimeRange(start: start, end: end)
let asset = try? AVAsset(url: composition.url!)
return asset
}
}Video Export
Export Video
swift
class VideoExporter {
func exportComposition(_ composition: AVMutableComposition, to url: URL) async throws {
let exportSession = AVAssetExportSession(
asset: composition,
presetName: AVAssetExportPresetHighestQuality
)
exportSession.outputURL = url
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
await exportSession.export()
switch exportSession.status {
case .completed:
print("Export completed")
case .failed:
throw exportSession.error ?? ExportError.exportFailed
case .cancelled:
throw ExportError.exportCancelled
default:
break
}
}
enum ExportError: Error {
case exportFailed
case exportCancelled
}
}Video Effects
Add Effects
swift
class VideoEffectsProcessor {
func addWatermark(to video: URL, watermarkImage: UIImage, outputURL: URL) async throws {
let asset = AVAsset(url: video)
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
try? videoTrack.insertTimeRange(
CMTimeRange(start: .zero, duration: asset.duration),
of: asset.tracks(withMediaType: .video)[0],
at: .zero
)
// Create watermark layer
let watermarkLayer = CALayer()
let watermarkImage = watermarkImage.cgImage.flatMap { UIImage(cgImage: $0) }
let watermarkContent = watermarkLayer.contents(image: watermarkImage)
watermarkLayer.frame = CGRect(x: 20, y: 20, width: 100, height: 50)
// Apply watermark
let videoComposition = AVMutableVideoComposition(asset: asset)
videoComposition.layerInstructions = [AVMutableVideoCompositionLayerInstruction(assetVideoTrack: videoTrack.tracks.first!)]
// Export
let exporter = VideoExporter()
try await exporter.exportComposition(composition, to: outputURL)
}
func applyFilter(to video: URL, filter: CIFilter, outputURL: URL) async throws {
let asset = AVAsset(url: video)
let item = AVPlayerItem(asset: asset)
// Apply filter using AVVideoComposition
let videoComposition = AVMutableVideoComposition(asset: asset)
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
try? videoTrack.insertTimeRange(
CMTimeRange(start: .zero, duration: asset.duration),
of: asset.tracks(withMediaType: .video)[0],
at: .zero
)
// Apply filter
let compositionWithFilter = composition
let exportSession = AVAssetExportSession(asset: compositionWithFilter, presetName: AVAssetExportPresetHighestQuality)
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
await exportSession.export()
}
}Video Capture
Camera Recording
swift
import AVFoundation
class VideoCaptureManager: NSObject, AVCaptureFileOutputRecordingDelegate {
private var captureSession: AVCaptureSession?
private var videoOutput: AVCaptureMovieFileOutput?
private var captureDevice: AVCaptureDevice?
@Published var isRecording = false
func setupCaptureSession() {
let session = AVCaptureSession()
session.sessionPreset = .high
guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
return
}
captureDevice = camera
do {
let input = try AVCaptureDeviceInput(device: camera)
if session.canAddInput(input) {
session.addInput(input)
}
videoOutput = AVCaptureMovieFileOutput()
if session.canAddOutput(videoOutput) {
session.addOutput(videoOutput)
}
session.startRunning()
} catch {
print("Error setting up capture: \(error)")
}
}
func startRecording(to url: URL) {
guard let output = videoOutput else { return }
output.startRecording(to: url, recordingDelegate: self)
isRecording = true
}
func stopRecording() {
videoOutput?.stopRecording()
isRecording = false
}
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if let error = error {
print("Recording error: \(error)")
} else {
print("Recording saved to: \(outputFileURL)")
}
}
}Thumbnails
Generate Thumbnails
swift
class ThumbnailGenerator {
func generateThumbnail(from videoURL: URL, at time: CMTime, size: CGSize) -> UIImage? {
let asset = AVAsset(url: videoURL)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
imageGenerator.maximumSize = size
var thumbnailImage: UIImage?
DispatchQueue.main.sync {
thumbnailImage = imageGenerator.copyCGImage(at: time, actualTime: nil)
}
if let cgImage = thumbnailImage {
return UIImage(cgImage: cgImage)
}
return nil
}
func generateAllThumbnails(from videoURL: URL, interval: CMTime, size: CGSize) -> [UIImage] {
let asset = AVAsset(url: videoURL)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
imageGenerator.maximumSize = size
var thumbnails: [UIImage] = []
var currentTime = CMTime.zero
let duration = asset.duration
while currentTime < duration {
autoreleasepool {
if let cgImage = try? imageGenerator.copyCGImage(at: currentTime, actualTime: nil) {
let image = UIImage(cgImage: cgImage)
thumbnails.append(image)
}
currentTime = CMTimeAdd(currentTime, interval)
}
}
return thumbnails
}
}Best Practices
- 1Memory: Manage video memory carefully
- 2Performance: Optimize for smooth playback
- 3Export: Use appropriate export presets
- 4Storage: Handle large video files
- 5Testing: Test on various devices
- 6Quality: Balance quality and performance
- 7Compression: Use appropriate compression
- 8UX: Provide smooth user experience
AVFoundation provides comprehensive video processing capabilities!