As miniaturas de vídeo são uma pedra angular de aplicações modernas de mídia, oferecendo aos usuários uma visualização visual rápida sem necessidade de reproduzir o vídeo completo. No desenvolvimento do iOS, o AVFoundation fornece as ferramentas mais performantes e flexíveis para extrair imagens de miniaturas. Este artigo caminha através da construção de um gerador de miniaturas de vídeo personalizado que pode lidar com cenários do mundo real, incluindo arquivos locais e remotos, cache, manipulação de erros e ajuste de desempenho, usando Swift e AVFoundation.

Compreender o papel da AVFoundation na geração de miniaturas

AVFoundation é o principal framework da Apple para trabalhar com mídia audiovisual. Para geração de miniaturas, as classes-chave são AVAsset e AVAssetImageGenerator.

  • AVAsset: Representa um activo audiovisual cronometrado (um vídeo local ou remoto). Contém informações de faixas, duração e metadados.
  • AVAssetImageGenerator: Fornece métodos para obter CGImageRefs do ativo em intervalos de tempo específicos. Ele respeita a orientação de vídeo, aplica ajustes de espaço de cores e suporta a geração de massa assíncrona.

Usando essas classes diretamente, você pode controlar o tamanho, a precisão de tempo e a transformação da imagem, ao contrário dos atalhos UIKit que frequentemente escondem opções de configuração.

Configurar o Projeto

Comece importando AVFoundation para o seu arquivo Swift:

import AVFoundation

Você também precisa do UIKit para exibir as imagens geradas. Crie um a partir de um URL. Para arquivos locais, use . Para vídeos remotos, use e garanta que o ativo possa 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)

Gerando uma única miniatura

Para gerar uma miniatura em um momento especificado, crie uma e chame . Este método é síncrono e deve ser executado fora do thread principal para evitar bloquear a 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)")
}

Importante: Configuração para garante que a imagem resultante respeite os metadados de rotação do vídeo (retrato vs. paisagem). Sem isso, um vídeo vertical pode aparecer lateralmente.

Escolher o Tempo Certo

As miniaturas são frequentemente tiradas no início de um vídeo, mas considere usar um tempo que represente o conteúdo – por exemplo, 10% na duração. Para obter um ponto de tempo significativo, consulte a duração do ativo:

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

Manipulação de Erros e Casos de Contorno

A geração de miniaturas pode falhar por várias razões. Sempre lidar com erros graciosamente e fornecer interface de retorno.

  • Ativo inválido: O vídeo pode estar corrompido ou a URL inalcançável.
  • Vídeo em falta: Alguns ativos contêm apenas áudio; confira .
  • Tempo fora dos limites: Garantir que o tempo solicitado está entre e a duração do activo.
  • Erros de rede: Os ativos remotos podem não ser carregados. Use antes de gerar miniaturas.

Exemplo de propriedades de ativos pré-carregados:

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
 }
}

Personalizar a Saída de Miniaturas

AVAssetImageGenerator oferece várias propriedades para ajustar a imagem de saída.

Tamanho Máximo

Restrinja a imagem gerada a uma caixa delimitadora específica usando . Isso reduz a sobrecarga de memória e acelera a geração de vídeos grandes.

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

A imagem gerada será dimensionada para caber dentro dessas dimensões, preservando a relação de aspecto.

Tolerância de Tempo Requerida

Por padrão, retorna o quadro exato na hora solicitada (ou o mais próximo possível). Você pode trocar a precisão para a velocidade definindo e . Para miniaturas, uma tolerância de 0,5 segundos é frequentemente aceitável e mais rápida:

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

Modo de abertura

controla como a imagem gerada lida com abertura limpa vs. abertura de produção. Use para remover poeira de borda e produzir uma miniatura mais limpa:

imageGenerator.apertureMode = .cleanAperture

Aplicando Transformações

Se você precisar girar ou virar a imagem (por exemplo, para uma orientação específica), use Core Graphics transforma. Uma abordagem é capturar a imagem bruta e então aplicar uma CGAffineTransform manualmente. Alternativamente, você pode definir para (como mostrado) e confiar na orientação natural do ativo.

Gerando Múltiplas Miniaturas Eficientemente

Para grades de vídeo, limpeza de linha do tempo, ou antevisão de storyboards, você precisa muitas vezes de dezenas de miniaturas. Evite chamar em um loop no thread principal. Em vez disso, use o método assíncrono . Ele processa pedidos de tempo múltiplos de forma eficiente, alavancando o 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 bem e permite atualizar a interface incrementalmente. Lembre-se de chamar se o usuário deixar a tela para liberar recursos.

Trabalhando com Vídeos Remotos

Quando o URL de vídeo aponta para um servidor remoto, você deve esperar que o ativo esteja totalmente carregado ou pelo menos suas informações de faixa estejam disponíveis. Use com cabeçalhos de cache apropriados para evitar downloads repetidos.

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

Para aplicações de produção, considere as miniaturas de cache no disco para que o acesso repetido ao mesmo vídeo não re-ative a imagem. Use ou cache baseado em arquivos usando o URL de vídeo como uma chave.

Exemplo de estratégia de cache

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 isso com o gerador de imagem para evitar trabalho redundante.

Considerações sobre o desempenho

Gerar miniaturas é uma operação intensiva em CPU, especialmente para vídeos de alta resolução 4K. Siga as seguintes melhores práticas:

  • Sempre executar chamadas síncronas em uma fila de fundo (por exemplo, ]).
  • Defina um razoável para reduzir as dimensões da imagem de saída. Um tamanho de 320x240 é frequentemente suficiente para miniaturas de lista.
  • Use para permitir que o gerador pule o quadro mais próximo em vez de resolver o decodificador exato.
  • Cancele qualquer geração assíncrona em andamento quando a visão desaparecer.
  • Para exibir em uma área de coleção, gerar miniaturas no tamanho exato da célula e evitar redimensionar no tópico principal.

Integrando-se com a Concurrência Swift (Async/Await)

Se o seu aplicativo atingir o iOS 15+, você pode embrulhar a chamada de bloqueio em uma continuação para código limpo assync/await:

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)
 }
 }
 }
}

Personalizações Avançadas

Miniaturas com Composição de Vídeo

Se o seu vídeo usar um (por exemplo, sobreposição de marca d'água, ajustes de cor), você pode passá-lo para o gerador de imagem:

imageGenerator.videoComposition = myVideoComposition

A miniatura gerada refletirá a saída composta.

Gerando miniaturas para fotos ao vivo

Live Photos são uma combinação de um vídeo e uma imagem imóvel. Para extrair uma miniatura do vídeo subjacente, use o e peça uma URL de vídeo antes de inscrevê-lo no AVFoundation.

Acessibilidade e experiência do usuário

As miniaturas melhoram a digitalização visual, mas sempre fornecem uma alternativa de texto para usuários do VoiceOver. Use o na visualização da imagem, descrevendo o conteúdo do vídeo, se possível.

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

Recursos externos

Para uma compreensão mais profunda, consulte o seguinte:

Conclusão

Construir um gerador de miniaturas de vídeo personalizado com AVFoundation lhe dá liberdade total sobre a qualidade, o tempo e o desempenho da imagem. Ao aproveitar tanto de forma sincronizada como assíncrona, lidar com erros e resultados de cache, você pode oferecer uma experiência perfeita de usuário em seu aplicativo iOS. Se você está processando arquivos locais ou vídeos remotos, as técnicas aqui cobertas irão ajudá-lo a produzir miniaturas nítidas e conscientes do contexto de forma rápida e eficiente.