Video miniatyrer är en hörnsten i moderna medierika applikationer, som erbjuder användare en snabb visuell förhandsvisning utan att behöva spela hela videon. I iOS-utveckling ger AVFoundation de mest performanta och flexibla verktygen för att extrahera miniatyrbilder. Denna artikel går igenom att bygga en anpassad video miniatyrgenerator som kan hantera verkliga scenarier - inklusive lokala och avlägsna filer, cachning, felhantering och prestandajustering - med hjälp av Swift och AVFoundation.

Förstå AVFoundation roll i miniatyr generation

AVFoundation är Apples primära ram för att arbeta med audiovisuella medier. För miniatyrgenerering är nyckelklasserna ]]AVAsset] och ]]AVAssetImageGenerator]].

  • ]AVAsset[: Representerar en tidsbestämd audiovisuell tillgång (en lokal eller fjärrvideo). Den innehåller spårinformation, varaktighet och metadata.
  • ]AVAssetImageGenerator: Ger metoder för att erhålla CGImageRefs från tillgången vid specifika tidsintervaller. Den respekterar videoinriktning, tillämpar färgutrymmejusteringar och stöder asynkron bulkgenerering.

Med hjälp av dessa klasser ger du direkt full kontroll över bildstorlek, tids noggrannhet och omvandling, till skillnad från UIKit-genvägar som ofta döljer konfigurationsalternativ.

Ställa in projektet

Börja med att importera AVFoundation till din Swift-fil:

import AVFoundation

Du behöver också UIKit för att visa de genererade bilderna. Skapa en ] från en URL. För lokala filer, använd ]]. För fjärrvideor, använd ] och se till att tillgången kan initieras:

// Local
let localURL = Bundle.main.url(forResource: "sample", withExtension: "mp4")!
let asset = AVAsset(url: localURL)

// Remote
let remoteURL = URL(string: "https://example.com/video.mp4")!
let remoteAsset = AVAsset(url: remoteURL)

Generera en enda miniatyr

För att generera en miniatyr vid en viss tidpunkt, skapa en ] och ring ]. Denna metod är synkron och bör köras av huvudtråden för att undvika att blockera UI.

let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true

let time = CMTime(seconds: 2.0, preferredTimescale: 600) // 2 seconds into the video

do {
 let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
 let thumbnail = UIImage(cgImage: cgImage)
 // Update UI on main thread
 DispatchQueue.main.async {
 imageView.image = thumbnail
 }
} catch {
 print("Thumbnail generation failed: \(error.localizedDescription)")
}

]][[] Inställning ]] till ]]] säkerställer att den resulterande bilden respekterar videons rotationsmetadata (porträtt vs landskap). Utan detta kan en upprätt video visas i sidled.

Välj rätt tid

Tumnaglar tas ofta i början av en video, men överväga att använda en tid som representerar innehållet - t.ex. 10% i varaktigheten. För att få en meningsfull tidspunkt, fråga tillgångens varaktighet:

let duration = asset.duration
let midTime = CMTimeMultiplyByFloat64(duration, multiplier: 0.1)

Hantering av fel och Edge Cases

Tumnail generation kan misslyckas av flera skäl. hantera alltid fel graciöst och ge återgångs-UI.

  • ogiltig tillgång:] Videon kan vara korrumperad eller URL:en som inte kan nås.
  • ]Missing videospår: Vissa tillgångar innehåller endast ljud; kontrollera ].
  • ]] Tiden av gränser: Se till att den begärda tiden är mellan och tillgångens varaktighet.
  • ]Fel på nätet: ] Fjärrtillgångar kan inte laddas. Använd ] innan du genererar miniatyrbilder.

Exempel på preloading tillgång egenskaper:

let assetKeys = ["tracks", "duration", "playable"]
asset.loadValuesAsynchronously(forKeys: assetKeys) {
 var error: NSError?
 let status = asset.statusOfValue(forKey: "tracks", error: &error)
 if status == .loaded {
 // Safe to generate thumbnails
 } else {
 // Handle error
 }
}

Anpassa Thumbnail Output

AVAssetImageGenerator erbjuder flera egenskaper för att finjustera utgångsbilden.

Maximal storlek

Begränsa den genererade bilden till en specifik bundningsruta med ]. Detta minskar minnesöverhuvudet och påskyndar generationen för stora videor.

imageGenerator.maximumSize = CGSize(width: 640, height: 480)

Den genererade bilden kommer att skalas för att passa in i dessa dimensioner samtidigt som aspektförhållandet bevaras.

Begär tid tolerans

Som standard returnerar den exakta ramen vid den begärda tiden (eller så nära som möjligt) du kan handla noggrannhet för hastighet genom att ställa in ] och ]. För miniatyrbilder är en tolerans på 0,5 sekunder ofta acceptabel och snabbare:

imageGenerator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
imageGenerator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)

Aperture Mode

] kontrollerar hur den genererade bilden hanterar ren bländare vs produktion bländare. Använd för att ta bort kant damm och producera en renare miniatyr:

imageGenerator.apertureMode = .cleanAperture

Tillämpa transformationer

Om du behöver rotera eller vända bilden (t.ex. för en specifik orientering), använd Core Graphics transforms. Ett tillvägagångssätt är att fånga den råa bilden och sedan tillämpa en CGAffineTransform manuellt. Alternativt kan du ställa in till (som visas) och lita på tillgångens naturliga orientering.

Generera flera miniatyrer effektivt

För videorutnät, tidslinjeskruvning eller storyboard förhandsvisningar, behöver du ofta dussintals miniatyrbilder. Undvik att ringa ] i en slinga på huvudtråden. Använd istället den asynkrona metoden ]. Det behandlar flera tidsförfrågningar effektivt, utnyttjar intern parallellism.

var times: [NSValue] = []
for i in 0..<10 {
 let time = CMTime(seconds: Double(i) * 2.0, preferredTimescale: 600)
 times.append(NSValue(time: time))
}

imageGenerator.generateCGImagesAsynchronously(forTimes: times) { requestedTime, cgImage, actualTime, result, error in
 if result == .succeeded, let cgImage = cgImage {
 let thumbnail = UIImage(cgImage: cgImage)
 DispatchQueue.main.async {
 // Append to array or update collection view cell
 }
 } else {
 print("Failed at \(requestedTime): \(error?.localizedDescription ?? "unknown")")
 }
}

Denna metod skalar bra och låter dig uppdatera UI stegvis. Kom ihåg att ringa om användaren lämnar skärmen till fria resurser.

Arbeta med fjärrvideor

När video URL pekar på en fjärrserver, måste du vänta på att tillgången ska vara fullt laddad eller åtminstone dess spårinformation som ska vara tillgänglig. Använd med lämpliga caching-huvuden för att undvika upprepade nedladdningar.

let urlAsset = AVURLAsset(url: remoteURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
urlAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
 // Then create image generator and proceed
}

För produktionsappar, överväga cachning miniatyrer på disken så att upprepad tillgång till samma video inte återföder bilden. Använd ] eller filbaserad cache med hjälp av video URL som en nyckel.

Caching strategi Exempel

class ThumbnailCache {
 static let shared = ThumbnailCache()
 private let cache = NSCache<NSString, UIImage>()

 func thumbnail(for url: URL) -> UIImage? {
 return cache.object(forKey: url.absoluteString as NSString)
 }

 func setThumbnail(_ image: UIImage, for url: URL) {
 cache.setObject(image, forKey: url.absoluteString as NSString)
 }
}

Kombinera detta med bildgeneratorn för att undvika redundant arbete.

Prestanda överväganden

Genererande miniatyrbilder är en CPU-intensiv operation, särskilt för högupplösta 4K-videor. Följ dessa bästa praxis:

  • Alltid utföra synkrona samtal på en bakgrundskö (t.ex. ).
  • Ange en rimlig ] för att minska utgångsbilddimensionerna. En storlek på 320x240 är ofta tillräcklig för listtummor.
  • Använd för att låta generatorn hoppa över närmaste ram istället för att lösa den exakta avkodningen.
  • Avbryt alla asynkrona generationer i progress när utsikten försvinner.
  • För visning i en samlingsvy, generera miniatyrbilder på exakt cellstorlek och undvika storlek på huvudtråden.

Integrering med Swift Concurrency (Async/Await)

Om din app riktar sig till iOS 15+ kan du koppla in blockeringsanropet i en fortsättning på ren async/vänta-kod:

func generateThumbnail(forVideoAt url: URL, at time: CMTime) async throws -> UIImage {
 let asset = AVAsset(url: url)
 let imageGenerator = AVAssetImageGenerator(asset: asset)
 imageGenerator.appliesPreferredTrackTransform = true
 imageGenerator.maximumSize = CGSize(width: 640, height: 480)

 return try await withCheckedThrowingContinuation { continuation in
 let times = [NSValue(time: time)]
 imageGenerator.generateCGImagesAsynchronously(forTimes: times) { _, cgImage, _, result, error in
 if let cgImage = cgImage, result == .succeeded {
 let image = UIImage(cgImage: cgImage)
 continuation.resume(returning: image)
 } else {
 continuation.resume(throwing: error ?? ThumbnailError.generationFailed)
 }
 }
 }
}

Avancerade anpassningar

Tumnaglar med videosammansättning

Om din video använder en (t.ex. vattenstämpel överlager, färgjusteringar), kan du skicka den till bildgeneratorn:

imageGenerator.videoComposition = myVideoComposition

Den genererade miniatyren kommer att återspegla den sammansatta produktionen.

Generera miniatyrbilder för Live Photos

Live Photos är en kombination av en video och en stillbild. För att extrahera en miniatyr från den underliggande videon, använd ] och begära en video URL innan du matar den i AVFoundation.

Tillgänglighet och användarupplevelse

Thumbnails förbättra visuell skanning, men alltid ge ett textalternativ för VoiceOver-användare. Använd ] på bildvyn och beskriva videoinnehållet om möjligt.

imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Thumbnail of video: \(videoTitle)"

Externa resurser

För djupare förståelse, rådfråga följande:

Slutsats

Att bygga en anpassad video miniatyrgenerator med AVFoundation ger dig fullständig frihet över bildkvalitet, timing och prestanda. Genom att utnyttja både synkront och asynkront, hantera fel och cachningsresultat kan du leverera en sömlös användarupplevelse i din iOS-app. Oavsett om du behandlar lokala filer eller fjärrvideor, hjälper de tekniker som omfattas här dig att producera skarpa, kontext-medvetna miniatyrer snabbt och effektivt.