Ingeniería civil y estructural
Creación de un generador de miniaturas de vídeo personalizado en Ios con Avfoundation
Table of Contents
Las miniaturas de vídeo son una piedra angular de las aplicaciones modernas de medios, ofreciendo a los usuarios una vista previa visual rápida sin necesidad de reproducir el vídeo completo. En el desarrollo de iOS, AVFoundation proporciona las herramientas más performant y flexibles para extraer imágenes de miniatura. Este artículo camina a través de la construcción de un generador de miniaturas de vídeo personalizado que puede manejar escenarios de mundo real, incluyendo archivos locales y remotos, caching, manejo de errores y afinación de rendimiento.
Comprender el papel de la AVFoundation en la generación de miniatura
La AVFoundation es el marco primario de Apple para trabajar con medios audiovisuales. Para la generación de miniaturas, las clases clave son AVAsset] y AVAssetImageGenerator].
- AVAsset: Representa un activo audiovisual templado (vídeo local o remoto). Contiene información de pista, duración y metadatos.
- AVAssetImageGenerator: Proporciona métodos para obtener CGImageRefs from the asset at specific time intervals. Respets video orientation, aplica ajustes en el espacio de color y soporta la generación asincrónica de granel.
Usando estas clases directamente te da control completo sobre el tamaño de la imagen, la precisión del tiempo y la transformación, a diferencia de los atajos UIKit que a menudo ocultan opciones de configuración.
Configuración del proyecto
Comience por importar AVFoundation en su archivo Swift:
import AVFoundation
También necesita UIKit para mostrar las imágenes generadas. Cree un de una URL. Para los archivos locales, utilice . Para los vídeos remotos, utilice y asegure que el activo pueda ser inicializado:
// 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)
Generando un único Thumbnail
Para generar una miniatura en un momento determinado, crear un y llamar . Este método es sincrónico y debe ser corredo por el hilo principal para evitar bloquear la interfaz de usuario.
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)")
}
Importante:] Ajuste a asegura que la imagen resultante respete los metadatos de rotación del vídeo (portrait vs. landscape). Sin esto, un vídeo vertical puede aparecer de forma paralela.
Elegir el tiempo correcto
Las miniaturas se toman a menudo al comienzo de un vídeo, pero considerar el uso de un tiempo que representa el contenido, por ejemplo, 10% en la duración. Para obtener un punto de tiempo significativo, consultar la duración del activo:
let duration = asset.duration
let midTime = CMTimeMultiplyByFloat64(duration, multiplier: 0.1)
Manejo de errores y casos de borde
La generación de Thumbnail puede fallar por varias razones. Siempre manejar errores con gracia y proporcionar la interfaz de usuario descomposición.
- Activo inválido: El vídeo puede ser dañado o la URL inalcanzable.
- Medida de la pista de vídeo: Algunos activos contienen sólo audio; cheque .
- Tiempo fuera de los límites:] Asegurar que el tiempo solicitado esté entre y la duración del activo.
- Errores de red: Los activos remotos pueden no cargar. Use antes de generar miniaturas.
Ejemplo de propiedades de activos precargados:
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
}
}
Personalización de la producción de miniatura
AVAssetImageGenerator ofrece varias propiedades para ajustar la imagen de salida.
Tamaño máximo
Restrict the generated image to a specific bounding box using . Esto reduce la memoria overhead y acelera la generación para grandes videos.
imageGenerator.maximumSize = CGSize(width: 640, height: 480)
La imagen generada se escalará para adaptarse a estas dimensiones, preservando al mismo tiempo la relación de aspecto.
Solicitud de la tolerancia del tiempo
Por defecto, devuelve el marco exacto en el tiempo solicitado (o lo más cerca posible). Puede cambiar la precisión para la velocidad estableciendo y . Para las miniaturas, una tolerancia de 0,5 segundos es a menudo aceptable y más rápido:
imageGenerator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
imageGenerator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)
Modo de apertura
controla cómo la imagen generada maneja la abertura limpia vs. la abertura de la producción. Use para quitar el borde del polvo y producir una miniatura limpia:
imageGenerator.apertureMode = .cleanAperture
Transformaciones de aplicación
Si necesita girar o voltear la imagen (por ejemplo, para una orientación específica), utilice los gráficos de núcleo transforma. Un enfoque es capturar la imagen cruda y luego aplicar un CGAffineTransform manualmente. Alternativamente, puede establecer a ] (como se muestra) y confiar en la orientación natural del activo.
Generando múltiples miniaturas eficientemente
Para las redes de vídeo, el escruciamiento de la línea de tiempo o las vistas de la tabla de cuentos, a menudo necesita docenas de miniaturas. Evite llamar en un bucle en el hilo principal. En lugar de ello, utilice el método asincrónico . Procesa múltiples solicitudes de tiempo eficientemente, aprovechando el paralelismo interno.
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")")
}
}
Este método escala bien y le permite actualizar la interfaz de usuario incrementalmente. Recuerde llamar si el usuario deja la pantalla a recursos gratuitos.
Trabajar con Vídeos Remotos
Cuando el vídeo URL apunta a un servidor remoto, debe esperar que el activo esté completamente cargado o al menos su información de pista para estar disponible. Use con cabeceras de caché apropiadas para evitar descargas repetidas.
let urlAsset = AVURLAsset(url: remoteURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
urlAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
// Then create image generator and proceed
}
Para aplicaciones de producción, considere las miniaturas de caché en disco para que el acceso repetido al mismo vídeo no vuelva a captar la imagen. Use o caché basado en archivos utilizando la URL de vídeo como clave.
Ejemplo de la estrategia de producción
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)
}
}
Combine esto con el generador de imagen para evitar el trabajo redundante.
Consideraciones de la ejecución
Generar miniaturas es una operación de gran intensidad de CPU, especialmente para videos de alta resolución 4K. Siga estas mejores prácticas:
- Siempre realizar llamadas sincronizadas en una cola de fondo (por ejemplo, ).
- Establece un razonable para reducir las dimensiones de la imagen de salida. Un tamaño de 320x240 es a menudo suficiente para las miniaturas de la lista.
- Utilice para permitir que el generador se salte el marco más cercano en lugar de resolver el decodificador exacto.
- Cancele cualquier generación asincrónica en el progreso cuando la vista desaparece.
- Para mostrar en una vista de colección, generar miniaturas en el tamaño exacto de la célula y evitar el redimensionamiento en el hilo principal.
Integrando con Swift Concurrency (Async/Await)
Si tu app se dirige a iOS 15+, puedes envolver la llamada de bloqueo en una continuación para un código de espera/así como:
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)
}
}
}
}
Personalizaciones avanzadas
Tumbnails con composición de vídeo
Si su vídeo utiliza un (por ejemplo, sobrecapa de marca de agua, ajustes de color), puede pasarlo al generador de imagen:
imageGenerator.videoComposition = myVideoComposition
La miniatura generada reflejará la salida compuesta.
Generando miniaturas para fotos en vivo
Las fotos en vivo son una combinación de un vídeo y una imagen todavía. Para extraer una miniatura del vídeo subyacente, utilice el y solicite una URL de vídeo antes de alimentarla en AVFoundation.
Accesibilidad y experiencia de usuario
Las miniaturas mejoran el escaneo visual, pero siempre proporcionan una alternativa de texto para los usuarios de VoiceOver. Utilice el en la vista de la imagen, describiendo el contenido de vídeo si es posible.
imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Thumbnail of video: \(videoTitle)"
Recursos externos
Para un entendimiento más profundo, consulte lo siguiente:
- AVAssetImageGenerator Official Documentation
- Referencia de AVAsset
- WDC 2019: Consejos de rendimiento de la AVFoundation]
Conclusión
Construyendo un generador de miniaturas de vídeo personalizado con AVFoundation te da la libertad completa sobre la calidad de imagen, el tiempo y el rendimiento. Al aprovechar tanto sincronizadamente como asincrónicamente, manejar errores y obtener resultados de caché, puedes ofrecer una experiencia de usuario perfecta en tu aplicación iOS. Ya sea que estés procesando archivos locales o videos remotos, las técnicas que aquí se cubren te ayudarán a producir nítida, contextualmente.