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:

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.