Table of Contents
Videominiatyrer er en hjørnestein i moderne medierike programmer, som tilbyr brukere en rask visuell forhåndsvisning uten å måtte spille full video. I iOS-utviklingen, tilbyr AVFoundation de mest performant og fleksible verktøyene for å trekke ut miniatyrbilder. Denne artikkelen går gjennom å bygge en egendefinert videominiatyrgenerator som kan håndtere virkelige scenarier - inkludert lokale og eksterne filer, caching, feilhåndtering og ytelsesjustering - ved hjelp av Swift og AVFoundation.
Forstå AVFoundations rolle i miniatyrgenerasjon
AVFoundation er Apples primære rammeverk for å jobbe med audiovisuelle medier. For miniatyrgenerering er nøkkelklassene AVAsset og AVAssetImageGenerator].
- AVAsset: Representerer en timed audiovisuell ressurs (en lokal eller fjernvideo). Den inneholder sporinformasjon, varighet og metadata.
- AvaAssetImageGenerator: Gier metoder til å skaffe CGImageRefs fra ressursen med bestemte tidsintervaller. Den respekterer videoorientering, anvender fargeromjusteringer og støtter asynkron bulkgenerasjon.
Ved å bruke disse klassene direkte gir du full kontroll over bildestørrelse, tidsnøyaktighet og transformasjon, i motsetning til UIKit-snarveier som ofte skjuler konfigurasjonsalternativer.
Å sette opp prosjektet
Start med å importere AVFoundation til Swift-filen din:
import AVFoundation
Du trenger også UIKit for å vise de genererte bildene. Opprett en [[FLT: 1]] fra en URL. For lokale filer, bruk [[FLT: 2]]. For eksterne videoer, bruk [[FLT: 3]]] og sørg for at aktiva kan initialiseres:
// 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)
Oppretter en enkelt miniatyr
For å generere én miniatyr på et bestemt tidspunkt, opprette en og ring . Denne metoden er synkron og bør kjøres av hovedtråden for å unngå å blokkere 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)")
}
Viktig: Innstilling til ] sikrer det resulterende bildet respekterer videoens rotasjonsmetadata (portrett mot landskap). Uten dette kan en oppreist video vises sidelengs.
Velg riktig tid
Miniatyrer tas ofte i begynnelsen av en video, men vurdere å bruke en tid som representerer innholdet - for eksempel 10% i varigheten. For å få et meningsfullt tidspunkt, spør aktivens varighet:
let duration = asset.duration
let midTime = CMTimeMultiplyByFloat64(duration, multiplier: 0.1)
Håndtering av feil og kantsaker
Miniatyrgenerasjon kan mislykkes av flere grunner. Alltid håndtere feil med graciøshet og gi reserve UI.
- Ugyldige eiendeler: Videoen kan være ødelagt eller URL-en kan ikke nås.
- Smilende videospor: Noen eiendeler inneholder bare lyd; sjekk .
- Tid ut av grenser: Sørg for at det etterspurde klokkeslettet er mellom ] og aktivens varighet.
- Nettverksfeil: Fjerne eiendeler kan ikke lastes. Bruk før du genererer miniatyrbilder.
Eksempel på forhåndslastende eiendomsegenskaper:
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
}
}
Tilpasser miniatyrutgang
AVASsetImageGenerator tilbyr flere egenskaper for å finjustere utgangsbildet.
Maksimal størrelse
Begrens det genererte bildet til en bestemt avgrensende boks ved å bruke . Dette reduserer minneoverskuddet og øker produksjonen for store videoer.
imageGenerator.maximumSize = CGSize(width: 640, height: 480)
Det genererte bildet vil bli skalert for å passe innenfor disse dimensjonene mens det bevarer aspektforholdet.
Anmodning om tidstolerance
Som standard returnerer nøyaktig ramme på det valgte tidspunkt (eller så nær som mulig). Du kan bytte nøyaktighet for hastighet ved å sette og . For miniatyrer er en toleranse på 0,5 sekunder ofte akseptabel og raskere:
imageGenerator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
imageGenerator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)
Aperturmodus
kontrollerer hvordan det genererte bildet håndterer ren åpning vs. produksjonsåpning. Bruk til å fjerne kantstøv og produsere en renere miniatyr:
imageGenerator.apertureMode = .cleanAperture
Bruke transformasjoner
Hvis du trenger å rotere eller snu bildet (f.eks. for en bestemt orientering), bruk Core Graphics transformeres. En tilnærming er å fange det rå bildet og deretter bruke en CGafineTransform manuelt. Alternativt kan du angi til (som vist) og stole på den naturlige orienteringen til ressursen.
Oppretter flere miniatyrer effektivt
For videonett, tidslinje krabning eller storyboard forhåndsvisninger, trenger du ofte dusinvis av miniatyrer. Unngå å ringe i en sløyfe på hovedtråden. I stedet, bruk asynkron metode . Det behandler flere tidsforespørsler effektivt, utnytte intern parallellisme.
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")")
}
}
Denne metoden skalerer godt og lar deg oppdatere UI-en gradvis. Husk å ringe [[FLT: 29]] hvis brukeren forlater skjermen til gratis ressurser.
Arbeid med eksterne videoer
Når videoadressen peker på en ekstern server, må du vente på at ressursen skal lastes fullt ut eller i det minste sporinformasjonen skal være tilgjengelig. Bruk [[FLT: 30]] med passende cache-hoder for å unngå gjentatte nedlastinger.
let urlAsset = AVURLAsset(url: remoteURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
urlAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
// Then create image generator and proceed
}
For produksjonsapper, vurdere caching miniatures på disken slik at gjentatt tilgang til den samme videoen ikke gjenkjenne bildet. Bruk eller filbasert cache ved hjelp av video URL som en nøkkel.
Eksempel på kakestrategi
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)
}
}
Kombiner dette med bildegeneratoren for å unngå overflødig arbeid.
Performance vurderinger
Opprette miniatyrbilder er en CPU-intensiv operasjon, spesielt for høyoppløselige 4K-videoer. Følg disse beste praksisene:
- Utfør alltid synkrone samtaler på en bakgrunnskø (f.eks. ).
- Sett en rimelig for å redusere utgangsbildedimensjonene. En størrelse på 320x240 er ofte tilstrekkelig for listeminimum.
- Bruk for å tillate generatoren å hoppe over den nærmeste rammen i stedet for å løse den nøyaktige dekode.
- Avbryt enhver i-progress asynkron generasjon når utsikten forsvinner.
- For å vise i en samlingsvisning, generere miniatyrbilder i den nøyaktige cellestørrelsen og unngå endring av størrelse på hovedtråden.
Integrering med Swift Concurrency (Async/Await)
Hvis appen din måler iOS 15+, kan du pakke blokkeringssamtalen i en videreføring for ren async/wait-kode:
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)
}
}
}
}
Avanserte tilpasninger
Miniatyrer med videosammensetning
Hvis videoen din bruker en (f.eks. vannmerkeoverlegg, fargejusteringer), kan du sende den til bildegeneratoren:
imageGenerator.videoComposition = myVideoComposition
Den genererte miniatyrbilde vil gjenspeile den sammensatte utgangen.
Oppretter miniatyrbilder for Live Photos
Live Photos er en kombinasjon av en video og et stillbilde. For å trekke ut en miniatyrbilde fra den underliggende videoen, bruk [FLT: 40] og be om en video URL før du mater den til AVFoundation.
Tilgjengelighet og brukeropplevelse
Miniatyrer forbedrer visuell skanning, men gir alltid et tekstalternativ for VoiceOver-brukere. Bruk på bildevisningen, som beskriver videoinnholdet om mulig.
imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Thumbnail of video: \(videoTitle)"
Eksterne ressurser
For dypere forståelse, se følgende:
- AvaAssetImageGenerator Offisiell dokumentasjon]
- AvaAsset Reference]
- WWDC 2019: AVFoundation Performance Tips]
Konklusjon
Bygge en egendefinert videominiatyrgenerator med AVFoundation gir deg fullstendig frihet over bildekvalitet, timing og ytelse. Ved å utnytte både synkront og asynkront, håndteringsfeil og caching-resultater, kan du levere en sømløs brukeropplevelse i iOS-appen. Enten du behandler lokale filer eller eksterne videoer, vil teknikkene som er dekket her hjelpe deg å produsere skarpe, kontekst-aware-miniature raskt og effektivt.